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 шт):
Поскольку оверлей 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)
