Jak sprawić, by w .Net okno zawsze pozostawało na wierzchu?

95

Mam aplikację C # winforms, która uruchamia makro w innym programie. Drugi program będzie ciągle wyskakiwał okienka i ogólnie sprawiał, że rzeczy wyglądały, z braku lepszego słowa, szalone. Chcę zaimplementować przycisk anulowania, który zatrzyma proces, ale nie mogę sprawić, by okno pozostało na wierzchu. Jak to zrobić w C #?

Edycja: próbowałem TopMost = true; , ale drugi program ciągle wyskakuje swoje własne okna. Czy istnieje sposób, aby wysłać moje okno na górę co n milisekund?

Edycja: Sposób, w jaki to rozwiązałem, polegał na dodaniu ikony w zasobniku systemowym, która anuluje proces, klikając ją dwukrotnie. Ikona w zasobniku systemowym nie jest zasłonięta. Dziękuję wszystkim, którzy odpowiedzieli. Przeczytałem artykuł o tym, dlaczego nie ma okna „super na górze” ... logicznie nie działa.

jle
źródło
62
Tak, ustaw licznik czasu na co kilka milisekund, który ustawi twoją Form.TopMost na true. Następnie, aby było ciekawie, po załadowaniu „szalonego” programu odtwórz klip audio z Mortal Kombat „FIGHT!” :-P
BFree
2
Mogłeś pomyśleć, że twój komentarz był zabawny, możesz pomyśleć, że możesz wyśmiać złe praktyki. Mój problem polegał na utworzeniu menu kontekstowego, które unosi się nad formularzem z panelem flowlayoutpanel. Panel flowlayout można przewijać tylko wtedy, gdy wywołasz jego metodę Activate (). Focus () NIE jest wystarczający w pewnych okolicznościach. Po prostu nie będziesz mógł go przewijać. To kradnie fokus z menu kontekstowego, nawet jeśli ma wyłączne najwyższe = prawda! Jak każdy rozsądny człowiek wie, boską praktyką jest pozwolić aplikacjom winform działać w trybie MTAThread i nadać każdej formie własny wątek, co
upraszcza
1
Oto diabeł: pastebin.com/sMJX0Yav Działa bezbłędnie, bez migotania, a sen (1) wystarczy, aby nie wyczerpał poważnej wydajności. Kto w każdym razie zagląda do swojego menedżera zadań, kiedy on skupia się na menu kontekstowym? Miejmy nadzieję, że po zamknięciu menu kontekstowego trafi ono do pustego modułu obsługi wyjątków i umiera. Możesz jednak wbudować przerwę isDisposed.
Traubenfuchs
Właśnie opublikowałem tutaj moje rozwiązanie tego problemu: stackoverflow.com/questions/2546566/ ...
kfn
@Traubenfuchs To się nie powiedzie z powodu wyjątku operacji między wątkami. To powinno działać.
mekb

Odpowiedzi:

172

Form.TopMost będzie działać, chyba że inny program tworzy najwyższe okna.

Nie ma możliwości utworzenia okna, które nie jest zakryte przez nowe najwyższe okna innego procesu. Raymond Chen wyjaśnił dlaczego.

RossFabricant
źródło
11
Jeśli inni nowicjusze zobaczą to w 2016 roku i później, spróbujForm.ActiveForm.TopMost
Devil's Advocate
1
@ScottBeeson: To jest dobre. Aktualne informacje są bardzo potrzebne w tym dynamicznym świecie techno. Dzięki:).
Sandeep Kushwah
48

Szukałem, aby moja aplikacja WinForms była „Zawsze na wierzchu”, ale ustawienie „TopMost” nic dla mnie nie dało. Wiedziałem, że jest to możliwe, ponieważ robi to WinAmp (wraz z wieloma innymi aplikacjami).

Zadzwoniłem do pliku „user32.dll”. Nie miałem żadnych skrupułów, żeby to zrobić i działa świetnie. W każdym razie jest to opcja.

Najpierw zaimportuj następującą przestrzeń nazw:

using System.Runtime.InteropServices;

Dodaj kilka zmiennych do deklaracji klasy:

private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const UInt32 SWP_NOSIZE = 0x0001;
private const UInt32 SWP_NOMOVE = 0x0002;
private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;

Dodaj prototyp funkcji user32.dll:

[DllImport("user32.dll")] 
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

Następnie w swoim kodzie (dodałem wywołanie w Form_Load ()) dodaj wywołanie:

SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS);

Mam nadzieję, że to pomoże. Odniesienie

clamum
źródło
2
Działa to nie tylko dla aplikacji WinForms, ale także dla okien konsoli . Niezłe znalezisko!
rojo
Świetnie, mogę potwierdzić, że to działa. Ale jak mógłbym to zmienić z powrotem, aby nie być najlepszym? czy jest flaga HWND_BOTTOMMOST, którą możesz udostępnić?
Mark
Dobre pytanie, jeśli chcesz przełączać się między tą najwyższą umiejętnością a domyślnym zachowaniem (na przykład masz pole wyboru „Zawsze na wierzchu” dla zachowania okna). Sądzę, że może przy odpowiednich flagach byłoby to możliwe. Jeśli masz odpowiednie flagi, które opisują domyślne zachowanie okna (powiedzmy SWP_DEFAULT = 0x0003), możesz po prostu ponownie wywołać „SetWindowPos ()” z tymi flagami. Po prostu nie jestem pewien; Nie zaglądałem do tego. Powodzenia, jeśli tak, a jeśli ktoś to zrobi, dodaj to tutaj!
clamum
To nie działa jak zwykle w trybie gry na pełnym ekranie
pełnoekranowym Sajitha Rathnayake
2
@Mark Tak, jest flaga HWND_NOTOPMOST (= -2). Zobacz docs.microsoft.com/en-us/windows/win32/api/winuser/…
Kevin Vuilleumier,
23

Jeśli przez „wariowanie” masz na myśli, że każde okno wciąż odbiera fokus drugiemu, TopMost nie rozwiąże problemu.

Zamiast tego spróbuj:

CalledForm.Owner = CallerForm;
CalledForm.Show();

To pokaże formę „dziecka” bez kradzieży skupienia. Formularz podrzędny również pozostanie na wierzchu swojego rodzica, nawet jeśli rodzic jest aktywowany lub skoncentrowany. Ten kod działa łatwo tylko wtedy, gdy utworzono wystąpienie formularza podrzędnego z poziomu formularza właściciela. W przeciwnym razie może być konieczne ustawienie właściciela za pomocą interfejsu API.

Victor Stoddard
źródło
1
Dziękuję bardzo, wykorzystałem dokładnie to i zadziałało idealnie!
AvetisG
1
Dzięki… dokładnie to, czego szukałem
Sameera Kumarasingha
Ustawienie CalledForm.Ownerna siebie ( CalledForm) spowoduje System.ArgumentException: „Utworzono cykliczne odwołanie do sterowania. Kontrola nie może być własnością ani być rodzicem dla siebie ”.
mekb
2
Dlatego używasz CallerForm zamiast CalledForm :)
Jesper
16

Ustaw Form.TopMost

Reed Copsey
źródło
Próbowałem, to ... czy muszę to ciągle robić? „Szalony program” natychmiast przejmuje kontrolę ...
jle,
2
Nie - jeśli ustawisz swój form.TopMost = true, powinno działać. „Szalony” program również musi mieć ustawione okna dialogowe na TopMost, w takim przypadku nie można tego nadpisać.
Reed Copsey,
Nie jest to uczciwa walka. Dziękuję Ci.
jle
11

Miałem chwilową 5-minutową przerwę i zapomniałem podać pełny formularz w ten sposób:

  myformName.ActiveForm.TopMost = true;

Ale to, czego naprawdę chciałem, to TO!

  this.TopMost = true;
Dave
źródło
U mnie zadziałało idealnie. if (checkBox1.Checked == true) {this.TopMost = true; } else {this.TopMost = false; }
yosh,
6

Ustaw właściwość formularza na .TopMosttrue.

Prawdopodobnie nie chcesz zostawiać go w ten sposób przez cały czas: ustaw go, gdy rozpocznie się proces zewnętrzny, i odłóż go, gdy się zakończy.

Joel Coehoorn
źródło
5

Sposób, w jaki to rozwiązałem, polegał na utworzeniu ikony w zasobniku systemowym z opcją anulowania.

jle
źródło
5

Poniższy kod sprawia, że ​​okno zawsze pozostaje na wierzchu, a także sprawia, że ​​jest bezramowe.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace StayOnTop
{
    public partial class Form1 : Form
    {
        private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
        private const UInt32 SWP_NOSIZE = 0x0001;
        private const UInt32 SWP_NOMOVE = 0x0002;
        private const UInt32 TOPMOST_FLAGS = SWP_NOMOVE | SWP_NOSIZE;

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

        public Form1()
        {
            InitializeComponent();
            FormBorderStyle = FormBorderStyle.None;
            TopMost = true;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            SetWindowPos(this.Handle, HWND_TOPMOST, 100, 100, 300, 300, TOPMOST_FLAGS);
        }

        protected override void WndProc(ref Message m)
        {
            const int RESIZE_HANDLE_SIZE = 10;

            switch (m.Msg)
            {
                case 0x0084/*NCHITTEST*/ :
                    base.WndProc(ref m);

                    if ((int)m.Result == 0x01/*HTCLIENT*/)
                    {
                        Point screenPoint = new Point(m.LParam.ToInt32());
                        Point clientPoint = this.PointToClient(screenPoint);
                        if (clientPoint.Y <= RESIZE_HANDLE_SIZE)
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)13/*HTTOPLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)12/*HTTOP*/ ;
                            else
                                m.Result = (IntPtr)14/*HTTOPRIGHT*/ ;
                        }
                        else if (clientPoint.Y <= (Size.Height - RESIZE_HANDLE_SIZE))
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)10/*HTLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)2/*HTCAPTION*/ ;
                            else
                                m.Result = (IntPtr)11/*HTRIGHT*/ ;
                        }
                        else
                        {
                            if (clientPoint.X <= RESIZE_HANDLE_SIZE)
                                m.Result = (IntPtr)16/*HTBOTTOMLEFT*/ ;
                            else if (clientPoint.X < (Size.Width - RESIZE_HANDLE_SIZE))
                                m.Result = (IntPtr)15/*HTBOTTOM*/ ;
                            else
                                m.Result = (IntPtr)17/*HTBOTTOMRIGHT*/ ;
                        }
                    }
                    return;
            }
            base.WndProc(ref m);
        }

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams cp = base.CreateParams;
                cp.Style |= 0x20000; // <--- use 0x20000
                return cp;
            }
        }
    }
}
BK Krish
źródło
Zgadzam się z Alexanem - co sprawia, że ​​Twój program jest najważniejszy? Wygląda na to, że w rzeczywistości jest to po prostu stwierdzenie „topmost = true”, które w wielu przypadkach nie działa. Cała reszta kodu tak naprawdę nie rozwiązuje problemu.
Fhaab
4

Jaka jest inna aplikacja, którą próbujesz ukryć widoczność? Czy badałeś inne sposoby osiągnięcia zamierzonego efektu? Zrób to, zanim narażasz swoich użytkowników na takie nieuczciwe zachowanie, jakie opisujesz: to, co próbujesz zrobić, brzmi raczej tak, jak niektóre niegrzeczne witryny robią z oknami przeglądarki ...

Przynajmniej staraj się przestrzegać zasady najmniejszej niespodzianki . Użytkownicy oczekują, że będą mogli samodzielnie określić kolejność większości aplikacji. Nie wiesz, co jest dla nich najważniejsze, więc jeśli coś zmienisz, powinieneś skupić się na popychaniu drugiej aplikacji za wszystko, zamiast promować własną.

Jest to oczywiście trudniejsze, ponieważ Windows nie ma szczególnie wyrafinowanego menedżera okien. Sugerują się dwa podejścia:

  1. wyliczanie okien najwyższego poziomu i sprawdzanie, do którego procesu należą , porzucając ich kolejność z, jeśli tak . (Nie jestem pewien, czy istnieją metody ramowe dla tych funkcji WinAPI).
  2. Bawiąc się uprawnieniami procesu potomnego, aby uniemożliwić mu dostęp do pulpitu ... ale nie próbowałbym tego, dopóki inne podejście nie zawiodło, ponieważ proces potomny może skończyć się w stanie zombie, wymagając interakcji użytkownika.
Pontus Gagge
źródło
4

Dlaczego nie zrobić z formularza okna dialogowego:

myForm.ShowDialog();
Salim
źródło
1
Tak! To jest to, czego chciałem. Ustawienie TopMost = truewymusiło moją formę na wszystkim, łącznie z chromem, kiedy w rzeczywistości jest to tylko pudełko ustawień i potrzebowałem go na górze głównej formy. Wyrazy uznania dla osoby internetowej.
MDMoore313
3

Oto odpowiednik SetForegroundWindow:

form.Activate();

Widziałem ludzi robiących dziwne rzeczy, takie jak:

this.TopMost = true;
this.Focus();
this.BringToFront();
this.TopMost = false;

http://blog.jorgearimany.com/2010/10/win32-setforegroundwindow-equivalent-in.html

user2070066
źródło
Co jeśli nie chcę, aby moje okno było aktywne, chcę tylko, aby znajdowało się na samym szczycie (zawiera informacje, a nie interaktywność)? Pytam tylko dlatego, że faktycznie wydawanie "topmost = True" nie działa w moim przypadku (działa na systemach, a nie na innych).
Fhaab
Okazało się, że to działa dla nas: this.Show(); this.Activate(); this.BringToFront(); ale ta odpowiedź doprowadziła nas do rozwiązania. Dzięki!
jibbs
1

Wiem, że to jest stare, ale nie widziałem tej odpowiedzi.

W oknie (xaml) dodaj:

Deactivated="Window_Deactivated"

W kodzie dla Window_Deactivated:

private void Window_Deactivated(object sender, EventArgs e)
    {
        Window window = (Window)sender;
        window.Activate();
    }

Dzięki temu okno będzie na górze.

czekaj, co
źródło
1
Nie widzisz tej odpowiedzi, ponieważ pytanie dotyczy winform.
Kinetic
0

Opierając się na odpowiedzi clamum i komentarzu Kevina Vuilleumiera na temat drugiej flagi odpowiedzialnej za zachowanie, dokonałem tego przełącznika, który przełącza się między na górze a nie na górze za pomocą naciśnięcia przycisku.

private void button1_Click(object sender, EventArgs e)
    {
        if (on)
        {
            button1.Text = "yes on top";
            IntPtr HwndTopmost = new IntPtr(-1);
            SetWindowPos(this.Handle, HwndTopmost, 0, 0, 0, 0, TopmostFlags);
            on = false;
        }
        else
        {
            button1.Text = "not on top";
            IntPtr HwndTopmost = new IntPtr(-2);
            SetWindowPos(this.Handle, HwndTopmost, 0, 0, 0, 0, TopmostFlags);
            on = true;
        }
    }
Tóth Dániel
źródło