Comment faire en sorte qu'une fenêtre reste toujours au top dans .Net?

93

J'ai une application winforms C # qui exécute une macro dans un autre programme. L'autre programme fera continuellement apparaître des fenêtres et rendra généralement les choses folles, faute d'un meilleur mot. Je souhaite implémenter un bouton d'annulation qui arrêtera l'exécution du processus, mais je n'arrive pas à faire en sorte que la fenêtre reste au top. Comment faire cela en C #?

Edit: j'ai essayé TopMost = true; , mais l'autre programme continue d'afficher ses propres fenêtres par-dessus. Existe-t-il un moyen d'envoyer ma fenêtre vers le haut toutes les n millisecondes?

Edit: La façon dont j'ai résolu ce problème était en ajoutant une icône de la barre d'état système qui annulera le processus en double-cliquant dessus. L'icône de la barre d'état système n'est pas masquée. Merci à tous ceux qui ont répondu. J'ai lu l'article expliquant pourquoi il n'y a pas de fenêtre «super-dessus» ... cela ne fonctionne logiquement pas.

jle
la source
61
Oui, définissez une minuterie toutes les quelques millisecondes qui définira votre Form.TopMost sur true. Ensuite, juste pour rendre cela intéressant, lorsque le programme "fou" se charge, lisez le clip audio de Mortal Kombat "FIGHT!" :-P
BGratuit
2
Vous pourriez penser que votre commentaire était hilarant, vous pourriez penser que vous pourriez ridiculiser une mauvaise pratique. Mon problème était de créer un menu contextuel qui flotte sur un formulaire avec un flowlayoutpanel. Un flowlayoutpanel ne peut être défilé que si vous appelez sa méthode Activate (), Focus () n'est PAS suffisant dans certaines circonstances. Vous ne pourrez tout simplement pas le faire défiler. Cela vole le focus du menu contextuel même s'il a exclusif topmost = true! Comme toute personne sensée sait qu'il est divinement pratique de laisser vos applications winform fonctionner en mode MTAThread et de donner à chaque formulaire son propre thread, ce qui simplifie la solution:
Traubenfuchs
1
Voici, un diable: pastebin.com/sMJX0Yav Cela fonctionne parfaitement sans scintillement et le sommeil (1) est suffisant pour l'empêcher de vider les performances sérieuses. Qui continue de chercher dans son gestionnaire de tâches pendant qu'il se concentre sur un menu contextuel? Une fois que le menu contextuel se ferme, il est heureusement exécuté dans le gestionnaire d'exceptions vide et meurt. Vous pouvez cependant intégrer une pause isDisposed.
Traubenfuchs
Je viens de publier ma solution à ce problème ici: stackoverflow.com/questions/2546566/…
kfn
@Traubenfuchs Cela échouera en raison d'une exception d'opération Cross-thread. Cela devrait fonctionner.
mekb

Réponses:

171

Form.TopMost fonctionnera à moins que l'autre programme crée les fenêtres les plus hautes.

Il n'existe aucun moyen de créer une fenêtre qui n'est pas couverte par les nouvelles fenêtres supérieures d'un autre processus. Raymond Chen a expliqué pourquoi.

RossFabricant
la source
10
Au cas où d'autres débutants complets verraient cela en 2016 et au-delà, essayezForm.ActiveForm.TopMost
Devil's Advocate
1
@ScottBeeson: C'est bien. Des informations mises à jour sont très nécessaires dans ce monde techno dynamique. Merci:).
Sandeep Kushwah
47

Je cherchais à faire de mon application WinForms "Always on Top" mais le réglage "TopMost" n'a rien fait pour moi. Je savais que c'était possible parce que WinAmp fait cela (avec une foule d'autres applications).

Ce que j'ai fait, c'est d'appeler "user32.dll". Je n'ai eu aucun scrupule à le faire et cela fonctionne très bien. C'est une option, de toute façon.

Tout d'abord, importez l'espace de noms suivant:

using System.Runtime.InteropServices;

Ajoutez quelques variables à votre déclaration de classe:

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;

Ajouter un prototype pour la fonction 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);

Puis dans votre code (j'ai ajouté l'appel dans Form_Load ()), ajoutez l'appel:

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

J'espère que cela pourra aider. Référence

clamum
la source
2
Cela fonctionne non seulement pour les applications WinForms, mais également pour les fenêtres de console . Bonne trouvaille!
rojo
Nice, peut confirmer que cela fonctionne. Mais comment pourrais-je le changer pour ne pas être le meilleur? existe-t-il un indicateur HWND_BOTTOMMOST que vous pouvez partager?
Mark
Bonne question, si vous vouliez permuter entre cette capacité supérieure et le comportement par défaut (par exemple, vous avez une case à cocher "Toujours en haut" pour le comportement de la fenêtre). Je suppose qu'avec les bons drapeaux, ce serait peut-être possible. Si vous aviez les bons indicateurs qui décrivaient le comportement de la fenêtre par défaut (par exemple SWP_DEFAULT = 0x0003), alors vous pourriez simplement appeler à nouveau "SetWindowPos ()" avec ces indicateurs. Je ne suis simplement pas sûr; Je ne l'ai pas regardé. Bonne chance si vous le faites, et si quelqu'un l'ajoute ici!
clamum
Cela ne fonctionne pas en mode de jeu plein écran comme d'habitude
Sajitha Rathnayake
2
@Mark Oui, il y a un indicateur HWND_NOTOPMOST (= -2). Voir docs.microsoft.com/en-us/windows/win32/api/winuser/…
Kevin Vuilleumier
23

Si par "devenir fou" vous voulez dire que chaque fenêtre continue de voler le focus de l'autre, TopMost ne résoudra pas le problème.

Essayez plutôt:

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

Cela montrera la forme «enfant» sans qu'elle vole l'attention. Le formulaire enfant restera également au-dessus de son parent même si le parent est activé ou focalisé. Ce code ne fonctionne facilement que si vous avez créé une instance du formulaire enfant à partir du formulaire propriétaire. Sinon, vous devrez peut-être définir le propriétaire à l'aide de l'API.

Victor Stoddard
la source
1
Merci beaucoup, j'ai utilisé exactement cela et cela a parfaitement fonctionné!
AvetisG
1
Merci .. exactement ce que je cherchais
Sameera Kumarasingha
Mettre CalledForm.Ownerà lui-même ( CalledForm) provoquera un System.ArgumentException: 'Une référence de contrôle circulaire a été faite. Un contrôle ne peut pas être possédé par lui-même ni être parent. ''
mekb
2
C'est pourquoi vous utilisez CallerForm au lieu de CalledForm :)
Jesper
16

Définir Form.TopMost

Reed Copsey
la source
J'ai essayé, ceci ... ai-je besoin de le faire continuellement? Le `` programme fou '' prend le relais immédiatement ...
jle
2
Non - si vous définissez votre formulaire.TopMost = true, cela devrait fonctionner. Le programme «fou» doit également avoir ses boîtes de dialogue définies sur TopMost, auquel cas vous ne pouvez pas le remplacer.
Reed Copsey
Pas un combat loyal. Je vous remercie.
jle
11

J'ai eu un laps de temps momentané de 5 minutes et j'ai oublié de spécifier le formulaire en entier comme ceci:

  myformName.ActiveForm.TopMost = true;

Mais ce que je voulais vraiment, c'était CECI!

  this.TopMost = true;
Dave
la source
A fonctionné parfaitement pour moi. if (checkBox1.Checked == true) {this.TopMost = true; } else {this.TopMost = false; }
yosh
6

Définissez la .TopMostpropriété du formulaire sur true.

Vous ne voulez probablement pas le laisser de cette façon tout le temps: définissez-le lorsque votre processus externe démarre et remettez-le lorsqu'il se termine.

Joel Coehoorn
la source
5

La façon dont j'ai résolu ce problème était de créer une icône de la barre d'état système avec une option d'annulation.

jle
la source
5

Le code suivant permet à la fenêtre de toujours rester en haut et de la rendre sans cadre.

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
la source
D'accord avec Alexan - qu'est-ce qui fait de votre programme le meilleur? Il semble que ce ne soit en fait que l'instruction "topmost = true", qui ne fonctionne pas dans de nombreux cas. Tout le reste du code ne répond pas vraiment au problème.
Fhaab
4

Quelle est l'autre application dont vous essayez de supprimer la visibilité? Avez-vous étudié d'autres moyens d'obtenir l'effet souhaité? Veuillez le faire avant de soumettre vos utilisateurs à un comportement malveillant tel que vous le décrivez: ce que vous essayez de faire ressemble plutôt à ce que certains sites méchants font avec les fenêtres de navigateur ...

Au moins, essayez de respecter la règle de la moindre surprise . Les utilisateurs s'attendent à pouvoir déterminer eux-mêmes l'ordre z de la plupart des applications. Vous ne savez pas ce qui est le plus important pour eux, donc si vous changez quoi que ce soit, vous devriez vous concentrer sur le fait de pousser l'autre application derrière tout plutôt que de promouvoir la vôtre.

Ceci est bien sûr plus délicat, car Windows ne dispose pas d'un gestionnaire de fenêtres particulièrement sophistiqué. Deux approches se suggèrent:

  1. énumérer les fenêtres de niveau supérieur et vérifier à quel processus elles appartiennent , en supprimant leur ordre z si c'est le cas . (Je ne suis pas sûr qu'il existe des méthodes de cadre pour ces fonctions WinAPI.)
  2. Jouer avec les autorisations de processus enfants pour l'empêcher d'accéder au bureau ... mais je n'essaierais pas cela jusqu'à ce que l'autre approche échoue, car le processus enfant pourrait se retrouver dans un état zombie tout en nécessitant une interaction de l'utilisateur.
Pontus Gagge
la source
4

Pourquoi ne pas faire de votre formulaire une boîte de dialogue:

myForm.ShowDialog();
Salim
la source
1
Oui! C'est ce que je voulais. La configuration a TopMost = trueforcé mon formulaire au-dessus de tout, y compris le chrome, alors qu'en réalité, ce n'est qu'une boîte de paramètres et j'en avais besoin au-dessus du formulaire principal. Félicitations à votre internaute.
MDMoore313
3

Voici l'équivalent de SetForegroundWindow:

form.Activate();

J'ai vu des gens faire des choses bizarres comme:

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

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

user2070066
la source
Que faire si je ne veux pas que ma fenêtre soit active, je la veux juste en haut (informative, pas interactive)? Je demande seulement parce que l'émission d'un "topmost = True" ne fonctionne pas dans mon cas (cela fonctionne sur les systèmes, pas sur les autres).
Fhaab
J'ai trouvé que cela fonctionnait pour nous: this.Show(); this.Activate(); this.BringToFront(); mais cette réponse nous a amenés à cette solution. Merci!
jibbs
1

Je sais que c'est vieux, mais je n'ai pas vu cette réponse.

Dans la fenêtre (xaml) ajoutez:

Deactivated="Window_Deactivated"

Dans le code derrière pour Window_Deactivated:

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

Cela gardera votre fenêtre au top.

attends quoi
la source
1
Vous n'avez pas vu cette réponse car la question concerne winform.
Cinétique
1

Sur la base de la réponse de clamum et du commentaire de Kevin Vuilleumier sur l'autre drapeau responsable du comportement, j'ai fait cette bascule qui bascule entre en haut et pas en haut en appuyant sur un bouton.

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
la source