Mostrar um formulário sem roubar o foco?


140

Estou usando um formulário para mostrar notificações (ele aparece na parte inferior direita da tela), mas quando mostro esse formulário, ele rouba o foco do formulário principal. Existe uma maneira de mostrar este formulário de "notificação" sem roubar o foco?

Respostas:


165

Hmmm, não basta substituir Form.ShowWithoutActivation o suficiente?

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

E se você não quiser que o usuário clique nessa janela de notificação, você pode substituir 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, Não posso acreditar que não o encontrei, desperdiçou uma tarde inteira!
Deerchao 14/05

2
Eu também precisava conjunto form1.Enabled = falsepara evitar controles internos da enfoque roubar
Jader Dias

23
E deixe o TopMost desligado.
Mckin

4
E se você deseja o TopMost, veja a outra resposta .
Roman Starkov 03/02

2
Os valores de WS_EX_NOACTIVATEe WS_EX_TOOLWINDOWsão 0x08000000e 0x00000080respectivamente.
Juan

69

Stolen 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 respondeu a isso, estou apenas expandindo-o colando diretamente o código. Alguém com direitos de edição pode copiá-lo por lá e excluí-lo para todo o que me importo;))


Fiquei me perguntando, ele realmente precisa que, se o formulário que ele exibe na parte inferior esquerda da tela está em outro tópico?
Patrick Desjardins

50
Acho inacreditável que ainda precisamos vincular arquivos DLL externos para interagir com os formulários. Estamos no .NET Framework versão 4 !! Hora de encerrar a Microsoft.
Maltrap 22/06/2009

9
A resposta aceita está incorreta. Procure ShowWithoutActivation
mklein

Basta adicionar frm.Hide (); no início da função ShowInactiveTopmost, se você desejar que seu formulário seja diretamente fora de foco. Não se esqueça: using System.Runtime.InteropServices; para fazer esse código rodar
Zitun

1
@Talha Este código não tem nada a ver com o evento Load. O evento Load é acionado quando o formulário está sendo carregado, não quando está sendo mostrado.
TheSoftwareJedi 31/01


12

Isto é o que funcionou para mim. Ele fornece o TopMost, mas sem roubar o 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;
       }
    }

Lembre-se de omitir a configuração do TopMost no designer do Visual Studio ou em outro lugar.

Isso é roubado, errado, emprestado, a partir daqui (clique em Soluções alternativas):

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


1
Mais acima + sem foco funciona e parece a mais limpa de todas as respostas.
feos 8/03/2017

O mais antigo está obsoleto desde o Windows 8, onde a Microsoft o pune ao usá-lo. O efeito é que, depois de abrir uma janela superior e depois fechá-la, o Windows move as outras janelas do seu aplicativo para o segundo plano. Esse certamente não é o comportamento desejado para o seu aplicativo. A Microsoft implementou isso porque, no passado, muitos programadores abusavam da parte superior mais intrusiva. Topmost é muito agressivo. Eu nunca uso isso.
Elmue

9

Fazer isso parece um hack, mas 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

Edit: Note, isso apenas gera um formulário já criado sem roubar o foco.


parece não funcionar aqui ... pode ser porque este "formulário de notificação" é aberto em outro segmento?
Matías

1
Provavelmente, nesse caso, você precisa fazer uma chamada this.Invoke () para chamar o método novamente como o encadeamento certo. Geralmente, trabalhar com formulários do encadeamento incorreto faz com que uma exceção seja lançada.
Matthew Scharley

Enquanto isso funciona, é um método hacky, como mencionado, e causou BSOD para mim sob certas condições, portanto, tenha cuidado com isso.
Raphael Smit

O mais antigo está obsoleto desde o Windows 8, onde a Microsoft o pune ao usá-lo. O efeito é que, depois de abrir uma janela superior e depois fechá-la, o Windows move as outras janelas do seu aplicativo para o segundo plano. Esse certamente não é o comportamento desejado para o seu aplicativo. A Microsoft implementou isso porque, no passado, muitos programadores abusavam da parte superior mais intrusiva. Topmost é muito agressivo. Eu nunca uso isso.
Elmue

9

O código de exemplo do pinvoke.net nas respostas de Alex Lyman / TheSoftwareJedi tornará a janela uma janela "no topo", o que significa que você não pode colocá-lo atrás das janelas normais depois que ele aparecer. Dada a descrição de Matias sobre o que ele quer usar, pode ser o que ele quer. Mas se você deseja que o usuário possa colocar sua janela atrás de outras janelas depois que você a abriu, basta usar HWND_TOP (0) em vez de HWND_TOPMOST (-1) na amostra.


6

No WPF, você pode resolvê-lo assim:

Na janela, coloque estes 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>

O último atributo é o que você precisa ShowActivated = "False".


4

Tenho algo parecido e simplesmente mostro o formulário de notificação e depois

this.Focus();

para trazer o foco de volta ao formulário principal.


Simples mas efetivo!
Tom

3

Crie e inicie o formulário de notificação em um thread separado e redefina o foco novamente para o formulário principal após a abertura do formulário. Peça ao Formulário de notificação que forneça um evento OnFormOpened que seja acionado a partir do Form.Shownevento. Algo assim:

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
}

Você também pode manter um identificador no objeto NotifcationForm, para que ele possa ser fechado programaticamente pelo formulário principal (frm.Close() ).

Alguns detalhes estão faltando, mas espero que isso o leve à direção certa.


Isso funcionará apenas se seu formulário for o originalmente ativo. Isso meio que contraria o objetivo principal desse tipo de notificação.
Alex Lyman

1
Hã? Esse é o objetivo da notificação - colocar e recuperar o foco novamente no formulário originalmente ativo.
Bob Nadler

2
Isso enfatiza apenas um formulário no seu aplicativo - e se algum outro programa estiver ativo no momento? Mostrar uma janela de notificação (geralmente para atualizar o usuário sobre o status do seu aplicativo) só é realmente útil quando ele não está assistindo ao seu aplicativo.
Alex Lyman

3

Você pode considerar que tipo de notificação você gostaria de exibir.

Se for absolutamente crítico informar ao usuário sobre algum evento, usar o Messagebox.Show seria a maneira recomendada, devido à sua natureza para bloquear outros eventos na janela principal, até que o usuário o confirme. Esteja ciente da cegueira pop-up, no entanto.

Se for menos que crítico, convém usar uma maneira alternativa de exibir notificações, como uma barra de ferramentas na parte inferior da janela. Você escreveu que exibe notificações no canto inferior direito da tela - a maneira padrão de fazer isso seria usar uma dica de balão com a combinação de um ícone na bandeja do sistema .


2
- dicas de balão não é uma opção porque pode ser desativado - A barra de estado pode ser escondido, se você tiver o programa minimizado qualquer forma obrigado por suas Recomendações
Matías

3

Isso funciona bem.

Consulte: OpenIcon - MSDN e 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

Você também pode lidar com isso apenas pela lógica, embora eu deva admitir que as sugestões acima, onde você acaba com um método BringToFront sem realmente roubar o foco, são as mais elegantes.

De qualquer forma, eu me deparei com isso e o resolvi usando uma propriedade DateTime para não permitir mais chamadas BringToFront se as chamadas já tivessem sido feitas recentemente.

Suponha uma classe principal, 'Core', que lida com, por exemplo, três formulários, 'Form1, 2 e 3'. Cada formulário precisa de uma propriedade DateTime e de um evento Activate que chame o Core para trazer janelas para a 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;
}

E, em seguida, crie o trabalho na Classe Principal:

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

Em uma nota lateral, se você deseja restaurar uma janela minimizada para seu estado original (não maximizado), use:

inForm.WindowState = FormWindowState.Normal;

Novamente, eu sei que essa é apenas uma solução de patch na falta de um BringToFrontWithoutFocus. É uma sugestão para evitar o arquivo DLL.


1

Não sei se isso é considerado como necro-postagem, mas foi o que fiz porque não consegui fazê-lo funcionar com os métodos "ShowWindow" e "SetWindowPos" do user32. E não, substituir "ShowWithoutActivation" não funciona nesse caso, pois a nova janela deve estar sempre em cima. Enfim, criei um método auxiliar que assume um formulário como parâmetro; quando chamado, mostra o formulário, o traz para a frente e o torna TopMost sem roubar o foco da janela atual (aparentemente, mas o usuário não 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);
        }
    }

0

Eu sei que pode parecer estúpido, mas isso funcionou:

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

Se você enviar a janela frontal para trás, ela poderá não ser mais exibida se as janelas em segundo plano sobrepuserem a nova em primeiro plano.
TamusJRoyce

0

Eu precisava fazer isso com minha janela TopMost. Eu implementei o método PInvoke acima, mas descobri que meu evento Load não estava sendo chamado como Talha acima. Eu finalmente consegui. Talvez isso ajude alguém. Aqui está a minha solução:

        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

Quando você cria um novo formulário usando

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

rouba o foco porque seu código não pode continuar em execução no formulário principal até que este seja fechado.

A exceção é usar o threading para criar um novo formulário e, em seguida, Form.Show (). Porém, verifique se o thread está visível globalmente, porque se você o declarar em uma função, assim que sua função terminar, seu thread terminará e o formulário desaparecerá.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.