Wie kann man dafür sorgen, dass ein Fenster in .Net immer oben bleibt?

92

Ich habe eine C # Winforms-App, die ein Makro in einem anderen Programm ausführt. Das andere Programm öffnet ständig Fenster und lässt die Dinge im Allgemeinen, mangels eines besseren Wortes, verrückt aussehen. Ich möchte eine Schaltfläche zum Abbrechen implementieren, die die Ausführung des Prozesses stoppt, aber ich kann anscheinend nicht das Fenster so einstellen, dass es oben bleibt. Wie mache ich das in C #?

Bearbeiten: Ich habe versucht, TopMost = true; , aber das andere Programm öffnet immer wieder seine eigenen Fenster. Gibt es eine Möglichkeit, mein Fenster alle n Millisekunden nach oben zu senden?

Bearbeiten: Ich habe dieses Problem gelöst, indem ich ein Taskleistensymbol hinzugefügt habe, das den Vorgang durch Doppelklick abbricht. Das Taskleistensymbol wird nicht verdeckt. Vielen Dank an alle, die geantwortet haben. Ich habe den Artikel darüber gelesen, warum es kein "Super-on-Top" -Fenster gibt ... es funktioniert logischerweise nicht.

jle
quelle
61
Ja, stellen Sie alle paar Millisekunden einen Timer ein, der Form.TopMost auf true setzt. Um es interessant zu machen, spielen Sie beim Laden des "verrückten" Programms den Audioclip von Mortal Kombat "FIGHT!" :-P
BFree
2
Sie könnten denken, Ihr Kommentar wäre lustig, Sie könnten denken, Sie könnten schlechte Praktiken lächerlich machen. Mein Problem bestand darin, ein Kontextmenü zu erstellen, das mit einem Flowlayoutpanel über ein Formular schwebt. Ein Flowlayoutpanel kann nur gescrollt werden, wenn Sie die Activate () -Methode aufrufen. Focus () reicht unter bestimmten Umständen NICHT aus. Sie können es einfach nicht scrollen. Das stiehlt den Fokus aus dem Kontextmenü, selbst wenn es exklusiv topmost = true hat! Wie jeder vernünftige Mensch weiß, ist es sehr praktisch, Ihre Winform-Anwendungen im MTAThread-Modus laufen zu lassen und jedem Formular einen eigenen Thread
zuzuweisen,
1
Siehe, ein Teufel: pastebin.com/sMJX0Yav Es funktioniert einwandfrei ohne zu flackern und der Schlaf (1) reicht aus, um ernsthafte Leistungen nicht zu beeinträchtigen. Wer schaut sowieso weiter in seinen Taskmanager, während er sich auf ein Kontextmenü konzentriert? Sobald das Kontextmenü geschlossen ist, läuft es hoffentlich in den leeren Ausnahmebehandler und stirbt. Sie könnten jedoch eine isDisposed-Pause einbauen.
Traubenfuchs
Ich habe gerade meine Lösung für dieses Problem hier veröffentlicht: stackoverflow.com/questions/2546566/…
kfn
@Traubenfuchs Das scheitert an einer Thread-Cross-Operation-Ausnahme. Das sollte funktionieren.
facepalm42

Antworten:

170

Form.TopMost funktioniert nur, wenn das andere Programm die obersten Fenster erstellt.

Es gibt keine Möglichkeit, ein Fenster zu erstellen, das nicht von neuen obersten Fenstern eines anderen Prozesses abgedeckt wird. Raymond Chen erklärte warum.

RossFabricant
quelle
10
Falls andere komplette Neulinge dies im Jahr 2016 und darüber hinaus sehen, versuchen Sie esForm.ActiveForm.TopMost
Devil's Advocate
@ ScottBeeson: Das ist gut. Aktualisierte Informationen sind in dieser technodynamischen Welt sehr wichtig. Vielen Dank:).
Sandeep Kushwah
47

Ich habe versucht, meine WinForms-Anwendung auf "Immer oben" zu setzen, aber die Einstellung "TopMost" hat nichts für mich getan. Ich wusste, dass es möglich war, weil WinAmp dies tut (zusammen mit einer Vielzahl anderer Anwendungen).

Ich habe "user32.dll" angerufen. Ich hatte keine Bedenken und es funktioniert großartig. Es ist sowieso eine Option.

Importieren Sie zunächst den folgenden Namespace:

using System.Runtime.InteropServices;

Fügen Sie Ihrer Klassendeklaration einige Variablen hinzu:

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;

Prototyp für die Funktion user32.dll hinzufügen:

[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);

Fügen Sie dann in Ihrem Code (ich habe den Aufruf in Form_Load () hinzugefügt) den Aufruf hinzu:

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

Hoffentlich hilft das. Referenz

Clamum
quelle
2
Dies funktioniert nicht nur für WinForms-Anwendungen, sondern auch für Konsolenfenster . Schöner Fund!
Rojo
Schön, kann bestätigen, dass dies funktioniert. Aber wie könnte ich es wieder ändern, um nicht der Beste zu sein? Gibt es ein HWND_BOTTOMMOST-Flag, das Sie freigeben können?
Mark
Gute Frage, wenn Sie zwischen dieser obersten Fähigkeit und dem Standardverhalten wechseln möchten (z. B. haben Sie ein Kontrollkästchen "Immer oben" für das Fensterverhalten). Ich würde vermuten, dass es vielleicht mit den richtigen Flaggen möglich wäre. Wenn Sie die richtigen Flags hatten, die das Standardfensterverhalten beschreiben (z. B. SWP_DEFAULT = 0x0003), können Sie mit diesen Flags einfach erneut "SetWindowPos ()" aufrufen. Ich bin mir einfach nicht sicher; Ich habe es nicht untersucht. Viel Glück, wenn Sie es tun, und wenn jemand es tut, fügen Sie es bitte hier hinzu!
Clamum
Dies funktioniert nicht wie gewohnt im
Vollbildmodus
2
@Mark Ja, es gibt ein Flag HWND_NOTOPMOST (= -2). Siehe docs.microsoft.com/en-us/windows/win32/api/winuser/…
Kevin Vuilleumier
23

Wenn Sie mit "verrückt werden" meinen, dass jedes Fenster dem anderen den Fokus stiehlt, wird TopMost das Problem nicht lösen.

Versuchen Sie stattdessen:

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

Dies zeigt die 'Kind'-Form, ohne dass sie den Fokus stiehlt. Das untergeordnete Formular bleibt auch dann über dem übergeordneten Formular, wenn das übergeordnete Element aktiviert oder fokussiert ist. Dieser Code funktioniert nur dann problemlos, wenn Sie eine Instanz des untergeordneten Formulars aus dem Eigentümerformular heraus erstellt haben. Andernfalls müssen Sie den Eigentümer möglicherweise über die API festlegen.

Victor Stoddard
quelle
1
Vielen Dank, ich habe genau das verwendet und es hat perfekt funktioniert!
AvetisG
1
Danke .. genau das, wonach ich gesucht habe
Sameera Kumarasingha
Die Einstellung CalledForm.Ownerauf sich selbst ( CalledForm) führt zu folgenden Ergebnissen System.ArgumentException: 'Es wurde eine Zirkelsteuerungsreferenz erstellt. Ein Steuerelement kann nicht im Besitz oder für sich selbst sein. '
facepalm42
2
Aus diesem Grund verwenden Sie CallerForm anstelle von CalledForm :)
Jesper
16

Stellen Sie Form.TopMost ein

Reed Copsey
quelle
Ich habe es versucht, muss ich es ständig tun? Das 'verrückte Programm' übernimmt sofort ...
jle
2
Nein - wenn Sie Ihr Formular festlegen.TopMost = true, sollte es funktionieren. Für das "verrückte" Programm müssen die Dialoge ebenfalls auf TopMost eingestellt sein. In diesem Fall können Sie sie nicht überschreiben.
Reed Copsey
Kein fairer Kampf. Danke dir.
Jle
11

Ich hatte eine kurze Zeitspanne von 5 Minuten und habe vergessen, das Formular vollständig wie folgt anzugeben:

  myformName.ActiveForm.TopMost = true;

Aber was ich wirklich wollte, war DIESES!

  this.TopMost = true;
Dave
quelle
Hat perfekt für mich funktioniert. if (checkBox1.Checked == true) {this.TopMost = true; } else {this.TopMost = false; }
yosh
hat perfekt für mich funktioniert. Danke dir.
Levani Titberia
6

Setzen Sie die .TopMostEigenschaft des Formulars auf true.

Sie möchten es wahrscheinlich nicht die ganze Zeit so lassen: Stellen Sie es ein, wenn Ihr externer Prozess beginnt, und legen Sie es zurück, wenn es beendet ist.

Joel Coehoorn
quelle
5

Ich habe dies gelöst, indem ich ein Taskleistensymbol mit einer Abbruchoption erstellt habe.

jle
quelle
5

Mit dem folgenden Code bleibt das Fenster immer oben und rahmenlos.

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
quelle
Stimmen Sie mit Alexan überein - was macht Ihr Programm am besten? Es scheint, als wäre es nur die "topmost = true" -Anweisung, die in vielen Fällen nicht funktioniert. Der ganze Rest des Codes beantwortet das Problem nicht wirklich.
Fhaab
4

Was ist die andere Anwendung, deren Sichtbarkeit Sie unterdrücken möchten? Haben Sie andere Möglichkeiten untersucht, um den gewünschten Effekt zu erzielen? Bitte tun Sie dies, bevor Sie Ihre Benutzer einem solchen Schurkenverhalten aussetzen, wie Sie es beschreiben: Was Sie versuchen zu tun, klingt eher so, wie bestimmte ungezogene Websites mit Browserfenstern tun ...

Versuchen Sie zumindest, sich an die Regel der geringsten Überraschung zu halten . Benutzer erwarten, dass sie die Z-Reihenfolge der meisten Anwendungen selbst bestimmen können. Sie wissen nicht, was für sie am wichtigsten ist. Wenn Sie also etwas ändern, sollten Sie sich darauf konzentrieren, die andere Anwendung hinter alles zu stellen, anstatt Ihre eigene zu bewerben.

Dies ist natürlich schwieriger, da Windows keinen besonders ausgeklügelten Fenstermanager hat. Zwei Ansätze bieten sich an:

  1. Auflisten von Fenstern der obersten Ebene und Überprüfen, zu welchem ​​Prozess sie gehören, und Löschen der Z-Reihenfolge, wenn dies der Fall ist . (Ich bin nicht sicher, ob es Framework-Methoden für diese WinAPI-Funktionen gibt.)
  2. Spielen Sie mit untergeordneten Prozessberechtigungen, um zu verhindern, dass auf den Desktop zugegriffen wird ... aber ich würde dies erst versuchen, wenn der andere Ansatz fehlgeschlagen ist, da der untergeordnete Prozess möglicherweise in einem Zombie-Status endet und Benutzerinteraktion erfordert.
Pontus Gagge
quelle
4

Warum machen Sie Ihr Formular nicht zu einem Dialogfeld:

myForm.ShowDialog();
Salim
quelle
1
Ja! Das wollte ich. Das Einstellen TopMost = truezwang mein Formular über alles, einschließlich Chrom, wenn es in Wirklichkeit nur ein Einstellungsfeld ist und ich es über dem Hauptformular brauchte. Ein großes Lob an Sie Internet-Person.
MDMoore313
3

Hier ist das SetForegroundWindow-Äquivalent:

form.Activate();

Ich habe Leute gesehen, die seltsame Dinge taten wie:

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

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

user2070066
quelle
Was ist, wenn ich nicht möchte, dass mein Fenster aktiv ist, sondern nur ganz oben (informativ, nicht interaktiv)? Ich frage nur, weil die Ausgabe eines "topmost = True" in meinem Fall nicht funktioniert (es funktioniert auf Systemen, nicht auf anderen).
Fhaab
Fand, dass dies für uns funktioniert: this.Show(); this.Activate(); this.BringToFront(); Aber diese Antwort brachte uns zu dieser Lösung. Vielen Dank!
Jibbs
1

Ich weiß, dass dies alt ist, aber ich habe diese Antwort nicht gesehen.

Fügen Sie im Fenster (xaml) Folgendes hinzu:

Deactivated="Window_Deactivated"

Im Code dahinter für Window_Deactivated:

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

Dadurch bleibt Ihr Fenster oben.

warte was
quelle
1
Sie haben diese Antwort nicht gesehen, da es sich bei der Frage um Winform handelt.
Kinetic
1

Basierend auf der Antwort von Clamum und Kevin Vuilleumiers Kommentar zu der anderen Flagge, die für das Verhalten verantwortlich ist, habe ich diesen Schalter gemacht, der per Knopfdruck zwischen oben und nicht oben wechselt .

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
quelle