¿Mostrar un formulario sin robar foco?

140

Estoy usando un formulario para mostrar notificaciones (aparece en la parte inferior derecha de la pantalla), pero cuando muestro este formulario roba el foco del formulario principal. ¿Hay alguna forma de mostrar este formulario de "notificación" sin robar foco?

Matías
fuente

Respuestas:

165

Hmmm, ¿no es simplemente anular Form.ShowWithoutActivation lo suficiente?

protected override bool ShowWithoutActivation
{
  get { return true; }
}

Y si tampoco desea que el usuario haga clic en esta ventana de notificación, puede anular CreateParams:

protected override CreateParams CreateParams
{
  get
  {
    CreateParams baseParams = base.CreateParams;

    const int WS_EX_NOACTIVATE = 0x08000000;
    const int WS_EX_TOOLWINDOW = 0x00000080;
    baseParams.ExStyle |= ( int )( WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW );

    return baseParams;
  }
}
Martin Plante
fuente
3
ShowWithoutActivation, ¡No puedo creer que no lo haya encontrado, desperdicié una tarde entera!
ciervo
2
También tenía que configurar form1.Enabled = falsepara evitar que los controles internos se robaran el foco
Jader Dias
23
Y deja TopMost apagado.
mklein
44
Y si quieres TopMost, mira la otra respuesta .
Roman Starkov
2
Los valores de WS_EX_NOACTIVATEy WS_EX_TOOLWINDOWson 0x08000000y 0x00000080respectivamente.
Juan
69

Robados de PInvoke.net 's ShowWindow método:

private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;

[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
     int hWnd,             // Window handle
     int hWndInsertAfter,  // Placement-order handle
     int X,                // Horizontal position
     int Y,                // Vertical position
     int cx,               // Width
     int cy,               // Height
     uint uFlags);         // Window positioning flags

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

static void ShowInactiveTopmost(Form frm)
{
     ShowWindow(frm.Handle, SW_SHOWNOACTIVATE);
     SetWindowPos(frm.Handle.ToInt32(), HWND_TOPMOST,
     frm.Left, frm.Top, frm.Width, frm.Height,
     SWP_NOACTIVATE);
}

(Alex Lyman respondió esto, solo lo estoy expandiendo pegando directamente el código. Alguien con derechos de edición puede copiarlo allí y eliminarlo por lo que me importa;))

TheSoftwareJedi
fuente
Me preguntaba, ¿realmente necesita que si la forma que muestra en la parte inferior izquierda de su pantalla está en otro hilo?
Patrick Desjardins
50
Me parece increíble que aún necesitemos vincularnos a archivos DLL externos para interactuar con los formularios. Estamos en .NET Framework versión 4 !! Es hora de envolverlo Microsoft.
Maltrap
9
La respuesta aceptada es incorrecta. Busque ShowWithoutActivation
mklein
Simplemente agregue frm.Hide (); al comienzo de la función ShowInactiveTopmost si desea que su formulario se desenfoque directamente. No olvide: usar System.Runtime.InteropServices; hacer que este código se ejecute
Zitun
1
@Talha Este código no tiene nada que ver con el evento Load. El evento Load se dispara cuando se carga el formulario, no cuando se muestra.
TheSoftwareJedi
14

Si está dispuesto a usar Win32 P / Invoke , puede usar el método ShowWindow (el primer ejemplo de código hace exactamente lo que desea).

Alex Lyman
fuente
12

Esto es lo que funcionó para mí. Proporciona TopMost pero sin robo de foco.

    protected override bool ShowWithoutActivation
    {
       get { return true; }
    }

    private const int WS_EX_TOPMOST = 0x00000008;
    protected override CreateParams CreateParams
    {
       get
       {
          CreateParams createParams = base.CreateParams;
          createParams.ExStyle |= WS_EX_TOPMOST;
          return createParams;
       }
    }

Recuerde omitir la configuración de TopMost en el diseñador de Visual Studio o en otro lugar.

Esto es robado, err, prestado, desde aquí (haga clic en Soluciones):

https://connect.microsoft.com/VisualStudio/feedback/details/401311/showwithoutactivation-is-not-supported-with-topmost

RenniePet
fuente
1
Topmost + desenfocado funciona, y parece ser la más limpia de todas las respuestas.
feos
Topmost está en desuso desde Windows 8, donde Microsoft lo castiga cuando lo usa. El efecto es que después de abrir una ventana superior y luego cerrarla, Windows mueve las otras ventanas de su aplicación a un segundo plano. Seguramente este no es el comportamiento deseado para su aplicación. Microsoft implementó esto porque en el pasado muchos programadores abusaron de los más intrusivos. Topmost es muy agresivo. Nunca lo uso
Elmue
9

Hacer esto parece un truco, pero parece funcionar:

this.TopMost = true;  // as a result the form gets thrown to the front
this.TopMost = false; // but we don't actually want our form to always be on top

Editar: Tenga en cuenta que esto simplemente genera un formulario ya creado sin robar foco.

Matthew Scharley
fuente
no parece funcionar aquí ... ¿podría deberse a que este "formulario de notificación" se abre en otro hilo?
Matías
1
Probablemente, en ese caso, debe hacer una llamada this.Invoke () para volver a llamar al método como el hilo correcto. En general, trabajar con formularios del hilo incorrecto provoca una excepción.
Matthew Scharley
Si bien esto funciona, es un método hacky como se mencionó y me ha causado BSOD en ciertas condiciones, así que tenga cuidado con esto.
Raphael Smit
Topmost está en desuso desde Windows 8, donde Microsoft lo castiga cuando lo usa. El efecto es que después de abrir una ventana superior y luego cerrarla, Windows mueve las otras ventanas de su aplicación a un segundo plano. Seguramente este no es el comportamiento deseado para su aplicación. Microsoft implementó esto porque en el pasado muchos programadores abusaron de los más intrusivos. Topmost es muy agresivo. Nunca lo uso
Elmue
9

El código de muestra de pinvoke.net en las respuestas de Alex Lyman / TheSoftwareJedi hará que la ventana sea una ventana "superior", lo que significa que no puede colocarla detrás de las ventanas normales después de que aparezca. Dada la descripción de Matias de para qué quiere usar esto, eso podría ser lo que quiere. Pero si desea que el usuario pueda colocar su ventana detrás de otras ventanas después de que la haya desplegado, simplemente use HWND_TOP (0) en lugar de HWND_TOPMOST (-1) en la muestra.

Micah
fuente
6

En WPF puedes resolverlo así:

En la ventana pon estos atributos:

<Window
    x:Class="myApplication.winNotification"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Notification Popup" Width="300" SizeToContent="Height"
  WindowStyle="None" AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True" Focusable="False" ShowActivated="False" >
</Window>

El último atributo es el que necesita ShowActivated = "False".

Ziketo
fuente
4

Tengo algo similar, y simplemente muestro el formulario de notificación y luego hago

this.Focus();

para volver a centrarnos en el formulario principal.

pkr298
fuente
Simple pero efectivo!
Tom
3

Cree e inicie el Formulario de notificación en un hilo separado y restablezca el enfoque a su formulario principal después de que se abra el Formulario. Haga que el formulario de notificación proporcione un evento OnFormOpened que se desencadena desde el Form.Shownevento. Algo como esto:

private void StartNotfication()
{
  Thread th = new Thread(new ThreadStart(delegate
  {
    NotificationForm frm = new NotificationForm();
    frm.OnFormOpen += NotificationOpened;
    frm.ShowDialog();
  }));
  th.Name = "NotificationForm";
  th.Start();
} 

private void NotificationOpened()
{
   this.Focus(); // Put focus back on the original calling Form
}

También puede mantener un identificador de su objeto NotifcationForm para que el formulario principal ( frm.Close()) pueda cerrarlo mediante programación .

Faltan algunos detalles, pero espero que esto te lleve en la dirección correcta.

Bob Nadler
fuente
Esto solo funcionará si su formulario era el formulario originalmente activo. Eso va en contra del objetivo principal de este tipo de notificación.
Alex Lyman
1
¿Eh? Ese es el propósito de la notificación: ponerla y recuperar el enfoque al formulario originalmente activo.
Bob Nadler
2
Esto solo enfoca un formulario en su aplicación: ¿qué sucede si algún otro programa está activo en ese momento? Mostrar una ventana de notificación (generalmente para brindarle al usuario una actualización sobre el estado de su aplicación) solo es realmente útil cuando no está viendo su aplicación.
Alex Lyman
3

Es posible que desee considerar qué tipo de notificación le gustaría mostrar.

Si es absolutamente crítico informar al usuario sobre algún evento, usar Messagebox.Show sería la forma recomendada, debido a su naturaleza, para bloquear cualquier otro evento en la ventana principal, hasta que el usuario lo confirme. Sin embargo, tenga en cuenta la ceguera emergente.

Si es menos que crítico, es posible que desee utilizar una forma alternativa de mostrar notificaciones, como una barra de herramientas en la parte inferior de la ventana. Escribió que muestra las notificaciones en la esquina inferior derecha de la pantalla; la forma estándar de hacerlo sería utilizando una punta de globo con la combinación de un icono de la bandeja del sistema .

Dragón plateado
fuente
2
- Las sugerencias de globos no son una opción porque se pueden deshabilitar - La barra de estado podría estar oculta si se minimiza el programa De todos modos, gracias por sus recomendaciones
Matías
3

Esto funciona bien

Ver: OpenIcon - MSDN y SetForegroundWindow - MSDN

using System.Runtime.InteropServices;

[DllImport("user32.dll")]
static extern bool OpenIcon(IntPtr hWnd);

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

public static void ActivateInstance()
{
    IntPtr hWnd = IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;

    // Restore the program.
    bool result = OpenIcon(hWnd); 
    // Activate the application.
    result = SetForegroundWindow(hWnd);

    // End the current instance of the application.
    //System.Environment.Exit(0);    
}
Jimi
fuente
1

También puede manejarlo solo por lógica, aunque tengo que admitir que las sugerencias anteriores donde termina con un método BringToFront sin realmente robar el foco es la más elegante.

De todos modos, me encontré con esto y lo resolví usando una propiedad DateTime para no permitir más llamadas BringToFront si las llamadas ya se hicieron recientemente.

Suponga una clase principal, 'Core', que maneja, por ejemplo, tres formas, 'Form1, 2 y 3'. Cada formulario necesita una propiedad DateTime y un evento Activate que llame a Core para traer ventanas al frente:

internal static DateTime LastBringToFrontTime { get; set; }

private void Form1_Activated(object sender, EventArgs e)
{
    var eventTime = DateTime.Now;
    if ((eventTime - LastBringToFrontTime).TotalMilliseconds > 500)
        Core.BringAllToFront(this);
    LastBringToFrontTime = eventTime;
}

Y luego crea el trabajo en Core Class:

internal static void BringAllToFront(Form inForm)
{
    Form1.BringToFront();
    Form2.BringToFront();
    Form3.BringToFront();
    inForm.Focus();
}

En una nota al margen, si desea restaurar una ventana minimizada a su estado original (no maximizada), use:

inForm.WindowState = FormWindowState.Normal;

Una vez más, sé que esto es solo una solución de parche en ausencia de un BringToFrontWithoutFocus. Es una sugerencia si desea evitar el archivo DLL.

Meta
fuente
1

No sé si esto se considera como publicación necro, pero esto es lo que hice, ya que no pude hacerlo funcionar con los métodos "ShowWindow" y "SetWindowPos" de user32. Y no, anular "ShowWithoutActivation" no funciona en este caso ya que la nueva ventana debe estar siempre arriba. De todos modos, creé un método auxiliar que toma una forma como parámetro; cuando se le llama, muestra el formulario, lo lleva al frente y lo convierte en TopMost sin robar el foco de la ventana actual (aparentemente lo hace, pero el usuario no lo notará).

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    static extern IntPtr SetForegroundWindow(IntPtr hWnd);

    public static void ShowTopmostNoFocus(Form f)
    {
        IntPtr activeWin = GetForegroundWindow();

        f.Show();
        f.BringToFront();
        f.TopMost = true;

        if (activeWin.ToInt32() > 0)
        {
            SetForegroundWindow(activeWin);
        }
    }
Domi
fuente
0

Sé que puede sonar estúpido, pero esto funcionó:

this.TopMost = true;
this.TopMost = false;
this.TopMost = true;
this.SendToBack();
Peter Mortensen
fuente
Si envía la ventana frontal hacia atrás, es posible que ya no se muestre si las ventanas de fondo se superponen a la nueva de primer plano.
TamusJRoyce
0

Necesitaba hacer esto con mi ventana TopMost. Implementé el método PInvoke anterior, pero descubrí que mi evento Load no se llamaba como Talha arriba. Finalmente lo logré. Quizás esto ayude a alguien. Aquí está mi solución:

        form.Visible = false;
        form.TopMost = false;
        ShowWindow(form.Handle, ShowNoActivate);
        SetWindowPos(form.Handle, HWND_TOPMOST,
            form.Left, form.Top, form.Width, form.Height,
            NoActivate);
        form.Visible = true;    //So that Load event happens
Steven Cvetko
fuente
-4

Cuando crea un nuevo formulario usando

Form f = new Form();
f.ShowDialog();

roba el foco porque su código no puede continuar ejecutándose en el formulario principal hasta que se cierre este formulario.

La excepción es mediante el uso de subprocesos para crear un nuevo formulario y luego Form.Show (). Sin embargo, asegúrese de que el hilo sea visible globalmente, porque si lo declara dentro de una función, tan pronto como su función salga, su hilo terminará y el formulario desaparecerá.

Freír
fuente
-5

Figurado a cabo: window.WindowState = WindowState.Minimized;.

Pawel Pawlowski
fuente