Afficher un formulaire sans voler le focus?


140

J'utilise un formulaire pour afficher les notifications (il apparaît en bas à droite de l'écran), mais lorsque j'affiche ce formulaire, il vole le focus du formulaire principal. Existe-t-il un moyen d'afficher ce formulaire de «notification» sans voler le focus?

Réponses:


165

Hmmm, il ne suffit pas de remplacer Form.ShowWithoutActivation?

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

Et si vous ne voulez pas non plus que l'utilisateur clique sur cette fenêtre de notification, vous pouvez remplacer 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;
  }
}

3
ShowWithoutActivation, je ne peux pas croire que je ne l'ai pas trouvé, j'ai perdu un après-midi entier!
deerchao

2
J'avais également besoin de régler form1.Enabled = falsepour empêcher les commandes internes de voler la mise au point
Jader Dias

23
Et laissez TopMost désactivé.
mklein le

4
Et si vous voulez TopMost, voyez l' autre réponse .
Roman Starkov

2
Les valeurs de WS_EX_NOACTIVATEet WS_EX_TOOLWINDOWsont 0x08000000et 0x00000080respectivement.
Juan

69

Stolen de PInvoke.net de ShowWindow procédé:

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 a répondu à cette question, je ne fais que l'étendre en collant directement le code. Quelqu'un avec les droits d'édition peut le copier là-bas et le supprimer pour tout ce que je veux;))


Je me demandais, a-t-il vraiment besoin que si le formulaire qu'il affiche en bas à gauche de son écran soit dans un autre fil?
Patrick Desjardins

50
Je trouve incroyable que nous ayons encore besoin de créer un lien vers des fichiers DLL externes pour interagir avec les formulaires. Nous sommes à .NET Framework version 4 !! Il est temps de l'envelopper Microsoft.
Maltrap

9
La réponse acceptée est incorrecte. Rechercher ShowWithoutActivation
mklein

Ajoutez simplement frm.Hide (); au début de la fonction ShowInactiveTopmost si vous souhaitez que votre formulaire ne soit pas focalisé directement. N'oubliez pas: en utilisant System.Runtime.InteropServices; pour faire fonctionner ce code
Zitun

1
@Talha Ce code n'a rien à voir avec l'événement Load. L'événement Load se déclenche lorsque le formulaire est en cours de chargement, pas lorsqu'il est affiché.
TheSoftwareJedi


12

C'est ce qui a fonctionné pour moi. Il fournit TopMost mais sans vol de concentration.

    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;
       }
    }

N'oubliez pas d'omettre de définir TopMost dans le concepteur Visual Studio ou ailleurs.

Ceci est volé, err, emprunté, à partir d'ici (cliquez sur Solutions de contournement):

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


1
Topmost + non focalisé fonctionne, et cela ressemble à la plus propre de toutes les réponses.
feos

Topmost est obsolète depuis Windows 8 où Microsoft vous punit lors de son utilisation. L'effet est qu'après avoir ouvert une fenêtre la plus haute et l'avoir fermée, Windows déplace les autres fenêtres de votre application vers l'arrière-plan. Ce n'est certainement pas le comportement souhaité pour votre application. Microsoft a implémenté cela parce que dans le passé, de nombreux programmeurs ont abusé des plus intrusifs. Topmost est très agressif. Je ne l'utilise jamais.
Elmue

9

Faire cela semble être un hack, mais cela semble fonctionner:

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

Edit: Notez que cela soulève simplement un formulaire déjà créé sans voler le focus.


ne semble pas fonctionner ici ... pourrait être parce que ce "formulaire de notification" est ouvert dans un autre fil?
Matías

1
Dans ce cas, vous devez probablement effectuer un appel this.Invoke () pour appeler à nouveau la méthode en tant que thread approprié. En général, travailler avec des formulaires du mauvais thread provoque la levée d'une exception.
Matthew Scharley

Bien que cela fonctionne, c'est une méthode piratée comme mentionné et qui a causé BSOD pour moi dans certaines conditions, alors méfiez-vous de cela.
Raphael Smit

Topmost est obsolète depuis Windows 8 où Microsoft vous punit lors de son utilisation. L'effet est qu'après avoir ouvert une fenêtre la plus haute et l'avoir fermée, Windows déplace les autres fenêtres de votre application vers l'arrière-plan. Ce n'est certainement pas le comportement souhaité pour votre application. Microsoft a implémenté cela parce que dans le passé, de nombreux programmeurs ont abusé des plus intrusifs. Topmost est très agressif. Je ne l'utilise jamais.
Elmue

9

L'exemple de code de pinvoke.net dans les réponses d'Alex Lyman / TheSoftwareJedi fera de la fenêtre une fenêtre "la plus haute", ce qui signifie que vous ne pouvez pas la mettre derrière des fenêtres normales après qu'elle soit apparue. Étant donné la description de Matias de ce qu'il veut utiliser, cela pourrait être ce qu'il veut. Mais si vous souhaitez que l'utilisateur puisse placer votre fenêtre derrière d'autres fenêtres après l'avoir ouverte, utilisez simplement HWND_TOP (0) au lieu de HWND_TOPMOST (-1) dans l'exemple.


6

Dans WPF, vous pouvez le résoudre comme ceci:

Dans la fenêtre, mettez ces attributs:

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

Le dernier attribut est celui dont vous avez besoin ShowActivated = "False".


4

J'ai quelque chose de similaire, et je montre simplement le formulaire de notification, puis je fais

this.Focus();

pour ramener le focus sur le formulaire principal.


Simple mais efficace!
Tom

3

Créez et démarrez le formulaire de notification dans un thread distinct et réinitialisez le focus sur votre formulaire principal une fois le formulaire ouvert. Demandez au formulaire de notification de fournir un événement OnFormOpened qui est déclenché à partir de l' Form.Shownévénement. Quelque chose comme ça:

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
}

Vous pouvez également conserver un handle vers votre objet NotifcationForm afin qu'il puisse être fermé par programme par le formulaire principal ( frm.Close()).

Certains détails manquent, mais j'espère que cela vous permettra d'aller dans la bonne direction.


Cela ne fonctionnera que si votre formulaire était le formulaire actif à l'origine. Cela va à l'encontre de l'objectif principal de ce type de notification.
Alex Lyman

1
Hein? C'est le but de la notification - la mettre en place et retrouver le focus sur le formulaire initialement actif.
Bob Nadler

2
Cela ne met l'accent que sur un formulaire de votre candidature - et si un autre programme est actif à ce moment-là? L'affichage d'une fenêtre de notification (généralement pour donner à l'utilisateur une mise à jour sur l'état de votre application) n'est vraiment utile que lorsqu'il ne regarde pas votre application.
Alex Lyman

3

Vous voudrez peut-être envisager le type de notification que vous souhaitez afficher.

S'il est absolument essentiel d'informer l'utilisateur d'un événement, l'utilisation de Messagebox.Show serait la méthode recommandée, en raison de sa nature, pour bloquer tout autre événement dans la fenêtre principale, jusqu'à ce que l'utilisateur le confirme. Soyez conscient de la cécité pop-up, cependant.

Si c'est moins que critique, vous pouvez utiliser un autre moyen d'afficher les notifications, comme une barre d'outils en bas de la fenêtre. Vous avez écrit que vous affichez des notifications en bas à droite de l'écran - la manière standard de le faire serait d'utiliser une info-bulle avec la combinaison d'une icône de la barre d'état système .


2
- Les astuces de ballons ne sont pas une option car elles peuvent être désactivées - La barre d'état pourrait être masquée si vous avez minimisé le programme Quoi qu'il en soit merci pour vos recommandations
Matías

3

Cela fonctionne bien.

Voir: OpenIcon - MSDN et 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);    
}

1

Vous pouvez également le gérer uniquement par la logique, bien que je doive admettre que les suggestions ci-dessus où vous vous retrouvez avec une méthode BringToFront sans réellement voler le focus sont les plus élégantes.

Quoi qu'il en soit, je suis tombé sur ce problème et l'ai résolu en utilisant une propriété DateTime pour ne pas autoriser d'autres appels BringToFront si des appels ont déjà été effectués récemment.

Supposons une classe principale, «Core», qui gère par exemple trois formulaires, «Form1, 2 et 3». Chaque formulaire a besoin d'une propriété DateTime et d'un événement Activate qui appellent Core pour mettre les fenêtres au premier plan:

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;
}

Et puis créez le travail dans la classe principale:

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

En passant, si vous souhaitez restaurer une fenêtre réduite à son état d'origine (non agrandi), utilisez:

inForm.WindowState = FormWindowState.Normal;

Encore une fois, je sais que ce n'est qu'une solution corrective en l'absence de BringToFrontWithoutFocus. Il s'agit d'une suggestion si vous souhaitez éviter le fichier DLL.


1

Je ne sais pas si cela est considéré comme une nécro-publication, mais c'est ce que j'ai fait car je ne pouvais pas le faire fonctionner avec les méthodes "ShowWindow" et "SetWindowPos" de user32. Et non, le remplacement de "ShowWithoutActivation" ne fonctionne pas dans ce cas puisque la nouvelle fenêtre doit être toujours en haut. Quoi qu'il en soit, j'ai créé une méthode d'assistance qui prend la forme d'un paramètre; lorsqu'il est appelé, il montre le formulaire, l'amène au premier plan et le rend TopMost sans voler le focus de la fenêtre actuelle (apparemment, c'est le cas, mais l'utilisateur ne le remarquera pas).

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

0

Je sais que cela peut sembler stupide, mais cela a fonctionné:

this.TopMost = true;
this.TopMost = false;
this.TopMost = true;
this.SendToBack();

Si vous envoyez la fenêtre avant vers l'arrière, il se peut qu'elle ne s'affiche plus si les fenêtres d'arrière-plan chevauchent la nouvelle fenêtre de premier plan.
TamusJRoyce

0

J'avais besoin de le faire avec ma fenêtre TopMost. J'ai implémenté la méthode PInvoke ci-dessus, mais j'ai trouvé que mon événement Load n'était pas appelé comme Talha ci-dessus. J'ai finalement réussi. Peut-être que cela aidera quelqu'un. Voici ma solution:

        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

-4

Lorsque vous créez un nouveau formulaire en utilisant

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

il vole le focus car votre code ne peut pas continuer à s'exécuter sur le formulaire principal tant que ce formulaire n'est pas fermé.

L'exception consiste à utiliser le thread pour créer un nouveau formulaire, puis Form.Show (). Assurez-vous cependant que le thread est globalement visible, car si vous le déclarez dans une fonction, dès que votre fonction se termine, votre thread se terminera et le formulaire disparaîtra.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.