У меня есть ToggleButton с KeyboardAccelerator, который переключает кнопку при каждом нажатии клавиши B. Проблема, с которой я столкнулся, заключается в том, что кнопку можно переключать с помощью сочетания клавиш только тогда, когда окно сфокусировано, и я хочу иметь возможность переключать эту кнопку независимо от того, сфокусировано это окно или нет.
Как мне сделать так, чтобы акселератор клавиатуры можно было использовать глобально, когда мое окно не в фокусе?
<ToggleButton Content = "Click me!">
<ToggleButton.KeyboardAccelerators>
<KeyboardAccelerator Key = "B" />
</ToggleButton.KeyboardAccelerators>
</ToggleButton>
Видео, на котором я переключаю кнопку, нажимая клавишу B с фокусом окна и без него:
Я просмотрел документацию по взаимодействию с клавиатурой и классу ToggleButton , чтобы узнать, есть ли какое-то свойство или способ установить глобальное ускорение клавиатуры, но не смог найти ничего о нем для моего конкретного проекта, который представляет собой Проект WinUI (C#), созданный с использованием Template Studio.
@SimonMourier Да, я примерно об этом и говорю. Я немного не понимаю, что вы имеете в виду под взаимодействием с Windows AppWindow. Я нашел в вашей ссылке функцию RegisterHotKey, буду ли я использовать ее или мне нужно что-то сделать с AppWindow? Я не слишком хорошо знаком с C# или .NET.





Чтобы создать глобальную горячую клавишу Windows, мы можем использовать функцию RegisterHotKey, но с WinUI3 это не так просто, поскольку эта функция работает путем отправки оконного сообщения, а цикл оконных сообщений WinUI3 не отображается.
Мы можем обойти это с помощью «подкласса» цикла оконных сообщений WinUI3, это делается в утилите WindowMessageHook ниже.
Вот как вы можете использовать его в окне WinUI3:
MainWindow.xaml:
<Window x:Class = "WinUI3App.MainWindow" xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel
HorizontalAlignment = "Center"
VerticalAlignment = "Center"
Orientation = "Horizontal">
<Button x:Name = "myButton" Click = "myButton_Click">Click</Button>
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Automation.Peers;
using Windows.System;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// hook this window's message
var hook = new WindowMessageHook(this);
Closed += (s, e) => hook.Dispose(); // unhook on close
hook.Message += (s, e) =>
{
const int WM_HOTKEY = 0x312;
if (e.Message == WM_HOTKEY)
{
// click on the button using UI Automation
var pattern = (ButtonAutomationPeer)FrameworkElementAutomationPeer.FromElement(myButton).GetPattern(PatternInterface.Invoke);
pattern.Invoke();
}
};
// register CTRL + B as a global hotkey
var hwnd = Win32Interop.GetWindowFromWindowId(AppWindow.Id);
var id = 1; // some arbitrary hotkey identifier
if (!RegisterHotKey(hwnd, id, MOD.MOD_CONTROL, VirtualKey.B))
throw new Win32Exception(Marshal.GetLastWin32Error());
Closed += (s, e) => UnregisterHotKey(hwnd, id); // unregister hotkey on window close
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
myButton.Content = "hello";
}
// interop code for Windows API hotkey functions
[DllImport("user32", SetLastError = true)]
private static extern bool RegisterHotKey(nint hWnd, int id, MOD fsModifiers, VirtualKey vk);
[DllImport("user32", SetLastError = true)]
private static extern bool UnregisterHotKey(nint hWnd, int id);
[Flags]
private enum MOD
{
MOD_ALT = 0x1,
MOD_CONTROL = 0x2,
MOD_SHIFT = 0x4,
MOD_WIN = 0x8,
MOD_NOREPEAT = 0x4000,
}
}
Утилита перехвата сообщений:
public class WindowMessageHook : IEquatable<WindowMessageHook>, IDisposable
{
private delegate nint SUBCLASSPROC(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData);
private static readonly ConcurrentDictionary<nint, WindowMessageHook> _hooks = new();
private static readonly SUBCLASSPROC _proc = SubclassProc;
public event EventHandler<MessageEventArgs> Message;
private nint _hWnd;
public WindowMessageHook(Window window) : this(GetHandle(window)) { }
public WindowMessageHook(nint hWnd)
{
if (hWnd == 0)
throw new ArgumentException(null, nameof(hWnd));
_hWnd = hWnd;
_hooks.AddOrUpdate(hWnd, this, (k, o) =>
{
if (Equals(o)) return o;
o.Dispose();
return this;
});
if (!SetWindowSubclass(hWnd, _proc, 0, 0))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
protected virtual void OnMessage(object sender, MessageEventArgs e) => Message?.Invoke(sender, e);
protected virtual void Dispose(bool disposing)
{
if (!disposing) return;
var hWnd = Interlocked.Exchange(ref _hWnd, IntPtr.Zero);
if (hWnd != IntPtr.Zero)
{
RemoveWindowSubclass(hWnd, _proc, 0);
_hooks.Remove(hWnd, out _);
}
}
~WindowMessageHook() { Dispose(disposing: false); }
public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); }
[DllImport("comctl32", SetLastError = true)]
private static extern bool SetWindowSubclass(nint hWnd, SUBCLASSPROC pfnSubclass, uint uIdSubclass, uint dwRefData);
[DllImport("comctl32", SetLastError = true)]
private static extern nint DefSubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam);
[DllImport("comctl32", SetLastError = true)]
private static extern bool RemoveWindowSubclass(nint hWnd, SUBCLASSPROC pfnSubclass, uint uIdSubclass);
private static nint GetHandle(Window window)
{
ArgumentNullException.ThrowIfNull(window);
return Win32Interop.GetWindowFromWindowId(window.AppWindow.Id);
}
private static nint SubclassProc(nint hWnd, uint uMsg, nint wParam, nint lParam, nint uIdSubclass, uint dwRefData)
{
if (_hooks.TryGetValue(hWnd, out var hook))
{
var e = new MessageEventArgs(hWnd, uMsg, wParam, lParam);
hook.OnMessage(hook, e);
if (e.Result.HasValue)
return e.Result.Value;
}
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
}
public override int GetHashCode() => _hWnd.GetHashCode();
public override string ToString() => _hWnd.ToString();
public override bool Equals(object obj) => Equals(obj as Window);
public virtual bool Equals(WindowMessageHook other) => other != null && _hWnd.Equals(other._hWnd);
}
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(nint hWnd, uint uMsg, nint wParam, nint lParam)
{
HWnd = hWnd;
Message = uMsg;
WParam = wParam;
LParam = lParam;
}
public nint HWnd { get; }
public uint Message { get; }
public nint WParam { get; }
public nint LParam { get; }
public virtual nint? Result { get; set; }
}
Вы имеете в виду глобальную горячую клавишу Windows? Вам придется использовать небольшое взаимодействие с использованием свойства WindowsAppWindow stackoverflow.com/questions/1820825/…