C# WPF: Как сделать, чтобы окно было поверх всех окон, но не сворачивало полноэкранные приложения?

В Windows при переключении языка через ALT+SHIFT (не наоборот) поверх всех окон появляется окошко, отображающее выбираемый язык.

введите сюда описание изображения


В приложении WPF понадобилось реализовать похожее окно:

Окно должно быть поверх всех окон и должно закрываться при нажатии ESC или ENTER. Следовательно, чтобы отслеживать эти кнопки через KeyDown-событие, окно должно находиться в фокусе.

Чтобы его нельзя было расфокусировать, я каждые 100 мс проверяю активно оно или нет:

// фокусировка окна каждые 100 мс
setFocusTimer = new DispatcherTimer();
setFocusTimer.Interval = TimeSpan.FromMilliseconds(100);
setFocusTimer.Tick += (sender, e) => { if (!ApplicationIsActivated()) Activate(); };
setFocusTimer.Start();

ApplicationIsActivated():

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
public static bool ApplicationIsActivated()
{
    var activatedHandle = GetForegroundWindow();
    if (activatedHandle == IntPtr.Zero)
        return false;

    var procId = Process.GetCurrentProcess().Id;
    int activeProcId;
    GetWindowThreadProcessId(activatedHandle, out activeProcId);

    return activeProcId == procId;
}

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


Проблема: если вызвать окно в полноэкранном приложении, (например, какой-нибудь игре), то приложение свернется и его нужно будет разворачивать после взаимодействия с окном.

Как будет правильно реализовать это окно?


Ответы (1 шт):

Автор решения: denisnumb

Поскольку оверлей DirectX должен отрисовываться для конкретного окна, а мне нужна реализация этого окошка поверх вообще всех окон, я решил отказаться от идеи отрисовки оверлея.

По сути решение оказалось на поверхности. Нашел его по подсказке Alexander Petrov.

Первое, что нужно сделать — это задать окну свойство Topmost="True", чтобы окно всегда было поверх остальных окон. А также ShowInTaskbar="False", чтобы окно не сворачивало полноэкранные приложения появляясь на панели задач при открытии.

При этом ни в коем случае нельзя активировать его, то есть вызывать Focus() и Activate(), чтобы окно не перехватывало фокус у других окон.

В целом это работает, но если случайно нажать на окно мышью — оно все равно получит фокус. Чтобы избежать этого, можно переопределить метод OnSourceInitialized() и запретить окну получать фокус. Решение нашел здесь.

protected override void OnSourceInitialized(EventArgs e)
{
    base.OnSourceInitialized(e);

    var helper = new WindowInteropHelper(this);
    SetWindowLong(helper.Handle, GWL_EXSTYLE,
        GetWindowLong(helper.Handle, GWL_EXSTYLE) | WS_EX_NOACTIVATE);
}   

private const int GWL_EXSTYLE = -20;
private const int WS_EX_NOACTIVATE = 0x08000000;

[DllImport("user32.dll")]
public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

В первоначальной версии вопроса я также отметил, что окну нужен фокус, чтобы срабатывало событие KeyDown для отслеживания нажатия некоторых клавиш. С отключением фокуса окна событие KeyDown использовать не получится. Вместо этого можно использовать глобальные горячие клавиши, через пакет NHotkey.Wpf (можно установить через NuGet)

→ Ссылка