¿Cómo hacer que una ventana permanezca siempre arriba en .Net?

93

Tengo una aplicación C # winforms que ejecuta una macro en otro programa. El otro programa continuamente abrirá ventanas y, en general, hará que las cosas se vean, a falta de una palabra mejor, loca. Quiero implementar un botón de cancelación que detendrá la ejecución del proceso, pero parece que no puedo hacer que la ventana permanezca en la parte superior. ¿Cómo hago esto en C #?

Editar: he probado TopMost = true; , pero el otro programa sigue apareciendo sus propias ventanas en la parte superior. ¿Hay alguna manera de enviar mi ventana a la parte superior cada n milisegundos?

Editar: La forma en que resolví esto fue agregando un ícono de la bandeja del sistema que cancelará el proceso al hacer doble clic en él. El icono de la bandeja del sistema no se cubre. Gracias a todos los que respondieron. Leí el artículo sobre por qué no hay una ventana 'súper en la parte superior' ... lógicamente no funciona.

jle
fuente
62
Sí, configure un temporizador para cada pocos milisegundos que establecerá su Form.TopMost en verdadero. Luego, solo para hacerlo interesante, cuando se cargue el programa "loco", reproduzca el clip de audio de Mortal Kombat "FIGHT!" :-P
bfree
2
Podría pensar que su comentario fue gracioso, podría pensar que podría ridiculizar la mala práctica. Mi problema fue crear un menú contextual que flota sobre un formulario con un panel de diseño de flujo. Un flowlayoutpanel solo se puede desplazar si llama a su método Activate (), Focus () NO es suficiente en ciertas circunstancias. Simplemente no podrá desplazarse por él. Eso roba el foco del menú contextual incluso si tiene exclusivo topmost = true! Como cualquier persona sensata sabe, es una práctica piadosa dejar que sus aplicaciones winform se ejecuten en modo MTAThread y dar a cada forma su propio hilo, lo que simplifica la solución:
Traubenfuchs
1
He aquí, un diablo: pastebin.com/sMJX0Yav Funciona a la perfección sin parpadear y el sueño (1) es suficiente para evitar que se agote el rendimiento. ¿Quién sigue buscando en su administrador de tareas de todos modos mientras se centra en un menú contextual? Una vez que se cierra el menú contextual, es de esperar que se encuentre con el controlador de excepciones vacío y muera. Sin embargo, puede construir una ruptura isDisposed.
Traubenfuchs
Acabo de publicar mi solución a este problema aquí: stackoverflow.com/questions/2546566/…
kfn
@Traubenfuchs Eso fallará debido a una excepción de operación entre subprocesos. Esto debería funcionar.
mekb

Respuestas:

171

Form.TopMost funcionará a menos que el otro programa esté creando las ventanas superiores.

No hay forma de crear una ventana que no esté cubierta por las nuevas ventanas superiores de otro proceso. Raymond Chen explicó por qué.

RossFabricante
fuente
10
En caso de que cualquier otro novato completo vea esto en 2016 y más allá, intenteForm.ActiveForm.TopMost
Abogado del diablo
1
@ScottBeeson: Esto es bueno. La información actualizada es muy necesaria en este mundo tecnodinámico. Gracias:).
Sandeep Kushwah
47

Estaba buscando hacer mi aplicación WinForms "Always on Top" pero configurar "TopMost" no hizo nada por mí. Sabía que era posible porque WinAmp hace esto (junto con muchas otras aplicaciones).

Lo que hice fue llamar a "user32.dll". No tuve reparos en hacerlo y funciona muy bien. De todos modos, es una opción.

Primero, importe el siguiente espacio de nombres:

using System.Runtime.InteropServices;

Agregue algunas variables a su declaración de clase:

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;

Agregue un prototipo para la función 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);

Luego, en su código (agregué la llamada en Form_Load ()), agregue la llamada:

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

Espero que ayude. Referencia

clamum
fuente
2
Esto funciona no solo para aplicaciones WinForms, sino también para ventanas de consola . ¡Buen hallazgo!
rojo
Bien, puedo confirmar que esto funciona. Pero, ¿cómo podría volver a cambiarlo para que no sea el mejor? ¿Existe una marca HWND_BOTTOMMOST que pueda compartir?
Mark
Buena pregunta, si desea cambiar entre esta habilidad superior y el comportamiento predeterminado (por ejemplo, tiene una casilla de verificación "Siempre visible" para el comportamiento de la ventana). Supongo que tal vez con las banderas adecuadas sería posible. Si tenía los indicadores correctos que describían el comportamiento predeterminado de la ventana (por ejemplo, SWP_DEFAULT = 0x0003), entonces podría simplemente llamar a "SetWindowPos ()" nuevamente con estos indicadores. Simplemente no estoy seguro; No lo he investigado. Buena suerte si lo hace, y si alguien lo hace, ¡agréguelo aquí!
clamum
Esto no funciona en el modo de juego de pantalla completa como de costumbre
Sajitha Rathnayake
2
@Mark Sí, hay una bandera HWND_NOTOPMOST (= -2). Ver docs.microsoft.com/en-us/windows/win32/api/winuser/…
Kevin Vuilleumier
23

Si por "volverse loco" quiere decir que cada ventana sigue robando el foco a la otra, TopMost no resolverá el problema.

En su lugar, intente:

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

Esto mostrará la forma 'infantil' sin que robe el foco. El formulario secundario también permanecerá encima de su padre incluso si el padre está activado o enfocado. Este código solo funciona fácilmente si ha creado una instancia del formulario secundario desde el formulario del propietario. De lo contrario, es posible que deba establecer el propietario mediante la API.

Victor Stoddard
fuente
1
¡Muchas gracias, he usado exactamente esto y funcionó perfectamente!
AvetisG
1
Gracias ... exactamente lo que estaba buscando
Sameera Kumarasingha
Establecer CalledForm.Owneren sí mismo ( CalledForm) causará un System.ArgumentException: 'Se ha hecho una referencia de control circular. Un control no puede ser propiedad de sí mismo ni ser padre de sí mismo.
mekb
2
Por eso usa CallerForm en lugar de CalledForm :)
Jesper
16

Establecer Form.TopMost

Reed Copsey
fuente
Intenté, esto ... ¿tengo que hacerlo continuamente? El 'programa loco' se hace cargo de inmediato ...
jle
2
No, si configura su formulario.TopMost = true, debería funcionar. El programa "loco" también debe tener sus diálogos configurados en TopMost, en cuyo caso, no puede anularlo.
Reed Copsey
No es una pelea justa. Gracias.
JLE
11

Tuve un lapso momentáneo de 5 minutos y olvidé especificar el formulario completo de esta manera:

  myformName.ActiveForm.TopMost = true;

¡Pero lo que realmente quería era ESTO!

  this.TopMost = true;
Dave
fuente
Funcionó perfecto para mí. if (checkBox1.Checked == true) {this.TopMost = true; } else {this.TopMost = false; }
yosh
6

Establezca la .TopMostpropiedad del formulario en verdadero.

Probablemente no quiera dejarlo así todo el tiempo: configúrelo cuando comience su proceso externo y vuelva a colocarlo cuando termine.

Joel Coehoorn
fuente
5

La forma en que resolví esto fue creando un ícono en la bandeja del sistema que tenía una opción de cancelación.

jle
fuente
5

El siguiente código hace que la ventana siempre permanezca en la parte superior y que no tenga marcos.

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
fuente
De acuerdo con Alexan: ¿qué es lo que hace que su programa sea el mejor? Parece que en realidad es solo la declaración "topmost = true", que no funciona en muchos casos. Todo el resto del código no responde realmente al problema.
Fhaab
4

¿Cuál es la otra aplicación cuya visibilidad está intentando suprimir? ¿Ha investigado otras formas de lograr el efecto deseado? Hágalo antes de someter a sus usuarios a un comportamiento deshonesto como el que está describiendo: lo que está tratando de hacer suena más bien a lo que ciertos sitios traviesos hacen con las ventanas del navegador ...

Al menos trata de adherirte a la regla de la menor sorpresa . Los usuarios esperan poder determinar el orden z de la mayoría de las aplicaciones por sí mismos. No sabes qué es lo más importante para ellos, así que si cambias algo, debes concentrarte en impulsar la otra aplicación detrás de todo en lugar de promocionar la tuya propia.

Por supuesto, esto es más complicado, ya que Windows no tiene un administrador de ventanas particularmente sofisticado. Se sugieren dos enfoques:

  1. enumerando las ventanas de nivel superior y verificando a qué proceso pertenecen , descartando su orden z si es así . (No estoy seguro de si existen métodos de marco para estas funciones de WinAPI).
  2. Jugando con los permisos del proceso hijo para evitar que acceda al escritorio ... pero no intentaría esto hasta que el otro enfoque fallara, ya que el proceso hijo podría terminar en un estado zombie y requerir la interacción del usuario.
Pontus Gagge
fuente
4

¿Por qué no convertir su formulario en un cuadro de diálogo?

myForm.ShowDialog();
Salim
fuente
1
¡Si! Esto es lo que quería. La configuración TopMost = trueforzó mi formulario encima de todo, incluido Chrome, cuando en realidad es solo un cuadro de configuración y lo necesitaba en la parte superior del formulario principal. Felicitaciones a su persona de Internet.
MDMoore313
3

Aquí está el equivalente de SetForegroundWindow:

form.Activate();

He visto gente haciendo cosas raras como:

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

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

usuario2070066
fuente
¿Qué pasa si no quiero que mi ventana esté activa, solo la quiero en la parte superior (informativa, no interactiva)? Solo pregunto porque en realidad emitir un "topmost = True" no funciona en mi caso (funciona en sistemas, no en otros).
Fhaab
Descubrí que esto funciona para nosotros: this.Show(); this.Activate(); this.BringToFront(); pero esta respuesta nos llevó a esa solución. ¡Gracias!
jibbs
1

Sé que esto es antiguo, pero no vi esta respuesta.

En la ventana (xaml) agregue:

Deactivated="Window_Deactivated"

En el código detrás de Window_Deactivated:

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

Esto mantendrá su ventana en la parte superior.

esperar lo
fuente
1
No vio esta respuesta porque la pregunta es sobre winform.
Kinetic
1

Basándome en la respuesta de clamum y el comentario de Kevin Vuilleumier sobre la otra bandera responsable del comportamiento, hice este interruptor que cambia entre arriba y no arriba con solo presionar un botón.

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
fuente