Показать форму, не отвлекая внимание?


145

Я использую форму для отображения уведомлений (она отображается в правом нижнем углу экрана), но когда я показываю эту форму, она перехватывает фокус у основной формы. Есть ли способ показать эту форму "уведомления", не отвлекая внимание?

Ответы:


167

Хммм, недостаточно ли просто переопределения Form.ShowWithoutActivation?

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

И если вы не хотите, чтобы пользователь щелкнул это окно уведомления, вы можете переопределить 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, не могу поверить, что не нашел, потратил целый день!
deerchao

3
Мне также нужно было установить, form1.Enabled = falseчтобы внутренние элементы управления не отвлекали внимание
Jader Dias

24
И оставьте TopMost выключенным.
mklein

4
И если вы хотите TopMost, посмотрите другой ответ .
Роман Старков

2
Значения WS_EX_NOACTIVATEи WS_EX_TOOLWINDOWравны 0x08000000и 0x00000080соответственно.
Хуан

70

Похищенный из Pinvoke.net «s ShowWindow методы:

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

(Алекс Лайман ответил на это, я просто расширяю его, напрямую вставляя код. Кто-то с правами редактирования может скопировать его туда и удалить, мне все равно;))


Мне было интересно, действительно ли ему нужно, чтобы форма, которую он отображает в нижнем левом углу экрана, находилась в другом потоке?
Патрик Дежарден,

50
Я считаю невероятным, что нам все еще нужно связывать внешние файлы DLL для взаимодействия с формами. Мы на платформе .NET версии 4 !! Пора закончить Microsoft.
Maltrap

9
Принятый ответ неверен. Ищите ShowWithoutActivation
mklein

Просто добавьте frm.Hide (); в начале функции ShowInactiveTopmost, если вы хотите, чтобы ваша форма не фокусировалась напрямую. Не забывайте: using System.Runtime.InteropServices; чтобы запустить этот код
Zitun

1
@Talha Этот код не имеет ничего общего с событием Load. Событие Load запускается, когда форма загружается, а не когда она отображается.
TheSoftwareJedi


13

Это то, что у меня сработало. Он обеспечивает TopMost, но без перехвата внимания.

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

Не забудьте опустить установку TopMost в конструкторе Visual Studio или где-либо еще.

Это украдено, ошибочно, позаимствовано отсюда (нажмите на обходные пути):

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


1
Самый верхний + несфокусированный работает, и это похоже на самый чистый из всех ответов.
feos 08

Topmost устарел с Windows 8, где Microsoft наказывает вас за его использование. Эффект состоит в том, что после открытия самого верхнего окна и его закрытия Windows перемещает другие окна вашего приложения в фоновый режим. Это определенно не желаемое поведение для вашего приложения. Microsoft реализовала это, потому что в прошлом многие программисты злоупотребляли очень назойливым верхом. Topmost очень агрессивен. Никогда не использую.
Elmue

9

Это похоже на взлом, но похоже, что это работает:

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

Изменить: обратите внимание, это просто вызывает уже созданную форму без кражи фокуса.


здесь не работает ... может быть, потому что эта "форма уведомления" открыта в другом потоке?
Матиас,

1
Вероятно, в этом случае вам нужно выполнить вызов this.Invoke (), чтобы снова вызвать метод как правильный поток. Однако обычно работа с формами из неправильного потока вызывает исключение.
Мэтью Шарли, 02

Хотя это действительно работает, как уже упоминалось, это хакерский метод, который при определенных условиях вызывает у меня BSOD, так что остерегайтесь этого.
Рафаэль Смит

Topmost устарел с Windows 8, где Microsoft наказывает вас за его использование. Эффект состоит в том, что после открытия самого верхнего окна и его закрытия Windows перемещает другие окна вашего приложения в фоновый режим. Это определенно не желаемое поведение для вашего приложения. Microsoft реализовала это, потому что в прошлом многие программисты злоупотребляли очень назойливым верхом. Topmost очень агрессивен. Никогда не использую.
Elmue

9

Пример кода с сайта pinvoke.net в ответах Alex Lyman / TheSoftwareJedi сделает окно «самым верхним» окном, что означает, что вы не можете поместить его за обычными окнами после его появления. Учитывая описание Матиаса того, для чего он хочет использовать это, это может быть то, что он хочет. Но если вы хотите, чтобы пользователь мог помещать ваше окно позади других окон после того, как вы его открыли, просто используйте HWND_TOP (0) вместо HWND_TOPMOST (-1) в примере.


6

В WPF это можно решить так:

В окошке укажите эти атрибуты:

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

Последний атрибут - это тот, который вам нужен ShowActivated = "False".


4

У меня есть что-то похожее, и я просто показываю форму уведомления, а затем делаю

this.Focus();

чтобы снова сфокусироваться на главной форме.


Простой, но эффективный!
Tom

3

Создайте и запустите форму уведомления в отдельном потоке и сбросьте фокус обратно на основную форму после открытия формы. Попросите форму уведомления предоставить событие OnFormOpened, которое запускается из Form.Shownсобытия. Что-то вроде этого:

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
}

Вы также можете сохранить дескриптор объекта NotifcationForm, чтобы его можно было программно закрыть с помощью основной формы Form ( frm.Close()).

Некоторые детали отсутствуют, но, надеюсь, это поможет вам двигаться в правильном направлении.


Это будет работать только в том случае, если ваша форма была изначально активной. Это противоречит основной цели такого уведомления.
Alex Lyman

1
А? Это цель уведомления - поднять его и вернуть фокус обратно к исходной активной форме.
Боб Надлер,

2
Это только фокусирует внимание на форме в вашем приложении - что, если в это время активна какая-то другая программа? Отображение окна уведомления (обычно для того, чтобы дать пользователю обновленную информацию о статусе вашего приложения) действительно полезно только тогда, когда они не смотрят ваше приложение.
Alex Lyman

3

Вы можете подумать, какое уведомление вы хотите отображать.

Если абсолютно необходимо сообщить пользователю о каком-либо событии, рекомендуется использовать Messagebox.Show из-за его характера для блокировки любых других событий в главном окне, пока пользователь не подтвердит это. Однако помните о всплывающей слепоте.

Если это менее критично, вы можете использовать альтернативный способ отображения уведомлений, например панель инструментов в нижней части окна. Вы писали, что вы показываете уведомления в правом нижнем углу экрана - стандартный способ сделать это - использовать всплывающую подсказку с комбинацией значка на панели задач .


2
- Всплывающие подсказки не подходят, потому что их можно отключить - Строка состояния может быть скрыта, если программа свернута. В любом случае спасибо за ваши рекомендации
Матиас

3

Это хорошо работает.

См .: OpenIcon - MSDN и 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

Вы также можете справиться с этим только с помощью логики, хотя я должен признать, что приведенные выше предложения, в которых вы заканчиваете методом BringToFront без фактического кражи фокуса, являются наиболее элегантными.

Как бы то ни было, я столкнулся с этим и решил это, используя свойство DateTime, чтобы запретить дальнейшие вызовы BringToFront, если вызовы были сделаны уже недавно.

Предположим, что базовый класс «Core» обрабатывает, например, три формы: «Form1, 2 и 3». Каждой форме требуется свойство DateTime и событие Activate, которое вызывает Core для вывода окон на передний план:

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

А затем создайте работу в Core Class:

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

Кстати, если вы хотите восстановить свернутое окно в исходное состояние (не развернуто), используйте:

inForm.WindowState = FormWindowState.Normal;

Опять же, я знаю, что это просто исправление из-за отсутствия BringToFrontWithoutFocus. Это означает, что вы хотите избежать использования файла DLL.


1

Я не знаю, считается ли это некро-публикацией, но это то, что я сделал, так как я не мог заставить его работать с методами user32 "ShowWindow" и "SetWindowPos". И нет, переопределение «ShowWithoutActivation» в этом случае не работает, так как новое окно всегда должно быть наверху. В любом случае, я создал вспомогательный метод, который принимает форму в качестве параметра; при вызове он показывает форму, выводит ее на передний план и делает ее TopMost, не крадя фокус текущего окна (очевидно, это так, но пользователь этого не заметит).

    [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

Я знаю, это может показаться глупым, но это сработало:

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

Если вы отправите переднее окно на задний план, оно может больше не отображаться, если фоновые окна перекрывают новое переднее окно.
TamusJRoyce

0

Мне нужно было сделать это с моим окном TopMost. Я реализовал метод PInvoke выше, но обнаружил, что мое событие Load не вызывается, как Talha выше. Наконец-то мне это удалось. Может это кому-то поможет. Вот мое решение:

        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

Когда вы создаете новую форму, используя

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

он крадет фокус, потому что ваш код не может продолжать выполнение в основной форме, пока эта форма не будет закрыта.

Исключением является использование потоков для создания новой формы, а затем Form.Show (). Однако убедитесь, что поток виден глобально, потому что, если вы объявите его в функции, как только ваша функция завершится, ваш поток завершится, и форма исчезнет.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.