Как сделать глобальное сочетание клавиш/горячую клавишу для кнопки?

У меня есть ToggleButton с KeyboardAccelerator, который переключает кнопку при каждом нажатии клавиши B. Проблема, с которой я столкнулся, заключается в том, что кнопку можно переключать с помощью сочетания клавиш только тогда, когда окно сфокусировано, и я хочу иметь возможность переключать эту кнопку независимо от того, сфокусировано это окно или нет.

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

<ToggleButton Content = "Click me!">
    <ToggleButton.KeyboardAccelerators>
        <KeyboardAccelerator Key = "B" />
    </ToggleButton.KeyboardAccelerators>
</ToggleButton>

Видео, на котором я переключаю кнопку, нажимая клавишу B с фокусом окна и без него:

Я просмотрел документацию по взаимодействию с клавиатурой и классу ToggleButton , чтобы узнать, есть ли какое-то свойство или способ установить глобальное ускорение клавиатуры, но не смог найти ничего о нем для моего конкретного проекта, который представляет собой Проект WinUI (C#), созданный с использованием Template Studio.

Вы имеете в виду глобальную горячую клавишу Windows? Вам придется использовать небольшое взаимодействие с использованием свойства WindowsAppWindow stackoverflow.com/questions/1820825/…

Simon Mourier 14.06.2024 09:09

@SimonMourier Да, я примерно об этом и говорю. Я немного не понимаю, что вы имеете в виду под взаимодействием с Windows AppWindow. Я нашел в вашей ссылке функцию RegisterHotKey, буду ли я использовать ее или мне нужно что-то сделать с AppWindow? Я не слишком хорошо знаком с C# или .NET.

Ryan Luu 14.06.2024 10:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чтобы создать глобальную горячую клавишу 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; }
}

Другие вопросы по теме