Pokaż formularz bez kradzieży ostrości?


140

Używam formularza do wyświetlania powiadomień (pojawia się w prawym dolnym rogu ekranu), ale kiedy pokazuję ten formularz, przejmuje fokus z głównego formularza. Czy istnieje sposób na wyświetlenie tego formularza „powiadomienia” bez kradzieży fokusu?

Odpowiedzi:


165

Hmmm, czy nie wystarczy po prostu nadpisać Form.ShowBoyoutActivation?

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

A jeśli nie chcesz, aby użytkownik klikał również to okno powiadomienia, możesz nadpisać 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
ShowBez Aktywacji, nie mogę uwierzyć, że go nie znalazłem, zmarnowałem całe popołudnie!
deerchao

2
Musiałem również ustawić, form1.Enabled = falseaby wewnętrzne sterowanie nie kradło ostrości
Jader Dias

23
I zostaw TopMost wyłączony.
mklein

4
A jeśli chcesz TopMost, zobacz drugą odpowiedź .
Roman Starkov

2
Wartości WS_EX_NOACTIVATEi WS_EX_TOOLWINDOWsą odpowiednio 0x08000000i 0x00000080.
Juan

69

Skradzione z PInvoke.net jest ShowWindow metodą:

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 odpowiedział na to pytanie, po prostu rozszerzam go, bezpośrednio wklejając kod. Ktoś z prawami do edycji może go tam skopiować i usunąć to, co mnie interesuje;))


Zastanawiałem się, czy naprawdę potrzebuje, aby formularz, który wyświetla w lewym dolnym rogu ekranu, był w innym wątku?
Patrick Desjardins

50
Uważam za niewiarygodne, że nadal musimy łączyć się z zewnętrznymi plikami DLL, aby wchodzić w interakcje z formularzami. Jesteśmy na platformie .NET w wersji 4 !! Czas to zakończyć Microsoft.
Maltrap

9
Przyjęta odpowiedź jest nieprawidłowa. Look for ShowBithoutActivation
mklein

Po prostu dodaj frm.Hide (); na początku funkcji ShowInactiveTopmost, jeśli chcesz, aby Twój formularz był bezpośrednio rozregulowany. Nie zapomnij: używać System.Runtime.InteropServices; aby ten kod działał
Zitun

1
@Talha Ten kod nie ma nic wspólnego ze zdarzeniem Load. Zdarzenie Load jest wyzwalane podczas ładowania formularza, a nie podczas wyświetlania.
TheSoftwareJedi


12

To właśnie zadziałało dla mnie. Zapewnia TopMost, ale bez kradzieży ostrości.

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

Pamiętaj, aby pominąć ustawienie TopMost w programie Visual Studio Designer lub w innym miejscu.

To jest skradzione, błędne, pożyczone, stąd (kliknij Obejście):

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


1
Najwyżej + nieostre działa i wygląda na najczystszą ze wszystkich odpowiedzi.
feos

Topmost jest przestarzały, ponieważ Windows 8, w którym Microsoft karze Cię za korzystanie z niego. Efekt jest taki, że po otwarciu okna znajdującego się na górze, a następnie zamknięciu go, system Windows przenosi pozostałe okna aplikacji na tło. Z pewnością nie jest to pożądane zachowanie Twojej aplikacji. Microsoft zaimplementował to, ponieważ w przeszłości wielu programistów nadużywało tego bardzo nachalnego elementu najwyższego. Najwyższy jest bardzo agresywny. Nigdy tego nie używam.
Elmue

9

Wydaje się, że to hack, ale wydaje się działać:

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

Edycja: Uwaga, to po prostu podnosi już utworzony formularz bez kradzieży ostrości.


wygląda na to, że nie działa tutaj ... może to być spowodowane tym, że ten „formularz powiadamiania” jest otwarty w innym wątku?
Matías

1
Prawdopodobnie w takim przypadku musisz wykonać wywołanie this.Invoke (), aby ponownie wywołać metodę jako właściwy wątek. Generalnie praca z formularzami z niewłaściwego wątku powoduje jednak zgłoszenie wyjątku.
Matthew Scharley

Chociaż to działa, jest to hacky metoda, jak wspomniano, i spowodowała dla mnie BSOD w pewnych warunkach, więc uważaj na to.
Raphael Smit

Topmost jest przestarzały, ponieważ Windows 8, w którym Microsoft karze Cię za korzystanie z niego. Efekt jest taki, że po otwarciu okna znajdującego się najwyżej, a następnie zamknięciu go, system Windows przenosi pozostałe okna aplikacji na tło. Z pewnością nie jest to pożądane zachowanie Twojej aplikacji. Firma Microsoft wdrożyła to, ponieważ w przeszłości wielu programistów nadużywało bardzo natrętnego najwyższego poziomu. Najwyższy jest bardzo agresywny. Nigdy tego nie używam.
Elmue

9

Przykładowy kod z pinvoke.net w odpowiedziach Alexa Lymana / TheSoftwareJedi sprawi, że okno będzie oknem „najwyższym”, co oznacza, że ​​nie można go umieścić za zwykłymi oknami po tym, jak się pojawi. Biorąc pod uwagę opis Matiasa, do czego chce tego użyć, może to być to, czego chce. Ale jeśli chcesz, aby użytkownik mógł umieścić twoje okno za innymi oknami po tym, jak je wyskoczyłeś, po prostu użyj HWND_TOP (0) zamiast HWND_TOPMOST (-1) w przykładzie.


6

W WPF można to rozwiązać w następujący sposób:

W oknie umieść te atrybuty:

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

Ostatni atrybut to ten, którego potrzebujesz ShowActivated = "False".


4

Mam coś podobnego i po prostu pokazuję formularz zgłoszeniowy a potem robię

this.Focus();

skupić się z powrotem na głównym formularzu.


Proste ale efektywne!
Tom

3

Utwórz i uruchom formularz powiadomienia w osobnym wątku i zresetuj fokus z powrotem do formularza głównego po otwarciu formularza. Poproś formularz powiadomienia o dostarczenie zdarzenia OnFormOpened, które jest uruchamiane ze Form.Shownzdarzenia. Coś takiego:

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
}

Możesz również zachować uchwyt do obiektu NotifcationForm, aby mógł być programowo zamknięty przez główny formularz Form (frm.Close() ).

Brakuje niektórych szczegółów, ale miejmy nadzieję, że poprowadzi Cię to we właściwym kierunku.


To zadziała tylko wtedy, gdy Twój formularz był pierwotnie aktywnym formularzem. Jest to sprzeczne z głównym celem tego rodzaju powiadomienia.
Alex Lyman

1
Co? Taki jest cel powiadomienia - aby je podnieść i wrócić do pierwotnie aktywnej formy.
Bob Nadler

2
To tylko skupia się na formularzu w twojej aplikacji - co, jeśli jakiś inny program jest aktywny w tym czasie? Pokazywanie okna powiadomień (zazwyczaj w celu poinformowania użytkownika o stanie aplikacji) jest naprawdę przydatne tylko wtedy, gdy nie ogląda on Twojej aplikacji.
Alex Lyman

3

Możesz zastanowić się, jakiego rodzaju powiadomienie chcesz wyświetlić.

Jeśli jest absolutnie krytyczne, aby poinformować użytkownika o jakimś zdarzeniu, zaleca się użycie Messagebox.Show, ze względu na jego naturę, aby zablokować wszelkie inne zdarzenia w oknie głównym, dopóki użytkownik tego nie potwierdzi. Uważaj jednak na wyskakującą ślepotę.

Jeśli jest to mniej niż krytyczne, możesz chcieć użyć alternatywnego sposobu wyświetlania powiadomień, na przykład paska narzędzi u dołu okna. Napisałeś, że wyświetlasz powiadomienia w prawym dolnym rogu ekranu - standardowym sposobem byłoby użycie końcówki dymku z kombinacją ikony zasobnika systemowego .


2
- Wskazówki dotyczące balonów nie są opcją, ponieważ można je wyłączyć - Pasek stanu może być ukryty, jeśli program został zminimalizowany. W każdym razie dzięki za rekomendacje
Matías

3

To działa dobrze.

Zobacz: OpenIcon - MSDN i 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

Państwo może obsługiwać go za pomocą logiki sam też, chociaż muszę przyznać, że powyższe sugestie, gdzie kończy się z metodą BringToFront bez faktycznie kradzież ostrość jest najbardziej elegancki jeden.

W każdym razie natknąłem się na to i rozwiązałem to, używając właściwości DateTime, aby nie zezwalać na dalsze wywołania BringToFront, jeśli połączenia były wykonywane już niedawno.

Załóżmy podstawową klasę „Core”, która obsługuje na przykład trzy formularze „Form1, 2 i 3”. Każdy formularz wymaga właściwości DateTime i zdarzenia Activate, które wywołuje Core w celu przeniesienia okien na wierzch:

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

A następnie stwórz pracę w Core Class:

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

Na marginesie, jeśli chcesz przywrócić zminimalizowane okno do jego pierwotnego stanu (nie zmaksymalizowane), użyj:

inForm.WindowState = FormWindowState.Normal;

Ponownie, wiem, że to tylko łatka bez BringToFrontWithoutFocus. Jest to sugestia, jeśli chcesz uniknąć pliku DLL.


1

Nie wiem, czy jest to uważane za nekro-wysyłanie, ale właśnie to zrobiłem, ponieważ nie mogę go uruchomić z metodami „ShowWindow” i „SetWindowPos” użytkownika 32. I nie, nadpisywanie „ShowWithoutActivation” nie działa w tym przypadku, ponieważ nowe okno powinno być zawsze na wierzchu. W każdym razie stworzyłem metodę pomocniczą, która przyjmuje postać jako parametr; po wywołaniu pokazuje formularz, przenosi go na wierzch i sprawia, że ​​jest to TopMost bez kradzieży fokusu bieżącego okna (najwyraźniej tak, ale użytkownik tego nie zauważy).

    [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

Wiem, że to może brzmieć głupio, ale to zadziałało:

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

Jeśli przesuniesz przednie okno do tyłu, może nie być już pokazywane, czy okna tła nachodzą na nowe okno pierwszego planu.
TamusJRoyce

0

Musiałem to zrobić z moim oknem TopMost. Zaimplementowałem powyższą metodę PInvoke, ale stwierdziłem, że moje zdarzenie Load nie jest wywoływane jak powyżej Talha. W końcu mi się udało. Może to komuś pomoże. Oto moje rozwiązanie:

        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

Podczas tworzenia nowego formularza za pomocą

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

kradnie fokus, ponieważ Twój kod nie może kontynuować wykonywania w głównym formularzu, dopóki ten formularz nie zostanie zamknięty.

Wyjątkiem jest użycie wątków do utworzenia nowego formularza, a następnie Form.Show (). Upewnij się jednak, że wątek jest widoczny globalnie, ponieważ jeśli zadeklarujesz go w funkcji, zaraz po zakończeniu funkcji wątek zakończy się, a formularz zniknie.


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.