Как я могу зарегистрировать глобальную горячую клавишу, чтобы говорить CTRL + SHIFT + (LETTER), используя WPF и .NET 3.5?

Я создаю приложение на C # с использованием WPF. Как привязать к некоторым клавишам?

Кроме того, как я могу привязаться к клавише Windows?


person w-ll    schedule 08.09.2008    source источник
comment
Из документации Win32 RegisterHotKey function: Сочетания клавиш, в которых задействована клавиша WINDOWS зарезервированы для использования операционной системой.   -  person Ian Kemp    schedule 11.05.2014
comment
@IanKemp: пока они не используются ОС, вы можете использовать их в своих приложениях. Да, именно поэтому MS предоставила OneNote Win + N и Win + Shift + N ;-)   -  person Mat M    schedule 03.09.2015


Ответы (11)


Я не уверен, что вы подразумеваете под глобальным здесь, но вот оно (я предполагаю, что вы имеете в виду команду на уровне приложения, например, Сохранить все, которая может быть запущена из любого места с помощью Ctrl + Shift + S.)

Вы найдете глобальный UIElement по вашему выбору, например, окно верхнего уровня, которое является родительским для всех элементов управления, для которых вам нужна эта привязка. Из-за всплытия событий WPF события дочерних элементов будут пузыриться вплоть до корня дерева элементов управления.

Теперь сначала вам нужно

  1. чтобы связать комбинацию клавиш с командой, используя InputBinding, как это
  2. затем вы можете подключить команду к своему обработчику (например, код, который вызывается SaveAll) через CommandBinding.

Для ключа Windows вы используете правильный Ключ перечислимого члена, Key.LWin или Key.RWin

public WindowMain()
{
   InitializeComponent();

   // Bind Key
   var ib = new InputBinding(
       MyAppCommands.SaveAll,
       new KeyGesture(Key.S, ModifierKeys.Shift | ModifierKeys.Control));
   this.InputBindings.Add(ib);

   // Bind handler
   var cb = new CommandBinding( MyAppCommands.SaveAll);
   cb.Executed += new ExecutedRoutedEventHandler( HandlerThatSavesEverthing );

   this.CommandBindings.Add (cb );
}

private void HandlerThatSavesEverthing (object obSender, ExecutedRoutedEventArgs e)
{
  // Do the Save All thing here.
}
person Gishu    schedule 08.09.2008
comment
См. Здесь более подробный способ создания MVVM stackoverflow.com/questions / 19697106 / create-key-binding-in-wpf - person anhoppe; 16.07.2015
comment
это не совсем глобальный (уровень приложения). Если вы откроете новое окно из MainWindow и нажмете там горячую клавишу, это не сработает. - person Dork; 07.08.2015
comment
Также это не глобально с точки зрения Windows. Если ваше приложение не в фокусе, это не сработает. - person user99999991; 14.08.2015
comment
К сожалению, это не глобальная горячая клавиша, вам нужно, чтобы ваше приложение было в фокусе, чтобы это работало ... - person Douglas Gaskell; 23.01.2016
comment
Что такое тип MyAppCommands? - person Neha; 26.03.2021

Это полноценное рабочее решение, надеюсь, поможет ...

Использование:

_hotKey = new HotKey(Key.F9, KeyModifier.Shift | KeyModifier.Win, OnHotKeyHandler);

...

private void OnHotKeyHandler(HotKey hotKey)
{
    SystemHelper.SetScreenSaverRunning();
}

Класс:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Mime;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace UnManaged
{
    public class HotKey : IDisposable
    {
        private static Dictionary<int, HotKey> _dictHotKeyToCalBackProc;

        [DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, UInt32 fsModifiers, UInt32 vlc);

        [DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        public const int WmHotKey = 0x0312;

        private bool _disposed = false;

        public Key Key { get; private set; }
        public KeyModifier KeyModifiers { get; private set; }
        public Action<HotKey> Action { get; private set; }
        public int Id { get; set; }

        // ******************************************************************
        public HotKey(Key k, KeyModifier keyModifiers, Action<HotKey> action, bool register = true)
        {
            Key = k;
            KeyModifiers = keyModifiers;
            Action = action;
            if (register)
            {
                Register();
            }
        }

        // ******************************************************************
        public bool Register()
        {
            int virtualKeyCode = KeyInterop.VirtualKeyFromKey(Key);
            Id = virtualKeyCode + ((int)KeyModifiers * 0x10000);
            bool result = RegisterHotKey(IntPtr.Zero, Id, (UInt32)KeyModifiers, (UInt32)virtualKeyCode);

            if (_dictHotKeyToCalBackProc == null)
            {
                _dictHotKeyToCalBackProc = new Dictionary<int, HotKey>();
                ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);
            }

            _dictHotKeyToCalBackProc.Add(Id, this);

            Debug.Print(result.ToString() + ", " + Id + ", " + virtualKeyCode);
            return result;
        }

        // ******************************************************************
        public void Unregister()
        {
            HotKey hotKey;
            if (_dictHotKeyToCalBackProc.TryGetValue(Id, out hotKey))
            {
                UnregisterHotKey(IntPtr.Zero, Id);
            }
        }

        // ******************************************************************
        private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
        {
            if (!handled)
            {
                if (msg.message == WmHotKey)
                {
                    HotKey hotKey;

                    if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
                    {
                        if (hotKey.Action != null)
                        {
                            hotKey.Action.Invoke(hotKey);
                        }
                        handled = true;
                    }
                }
            }
        }

        // ******************************************************************
        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // ******************************************************************
        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be _disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be _disposed.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this._disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    Unregister();
                }

                // Note disposing has been done.
                _disposed = true;
            }
        }
    }

    // ******************************************************************
    [Flags]
    public enum KeyModifier
    {
        None = 0x0000,
        Alt = 0x0001,
        Ctrl = 0x0002,
        NoRepeat = 0x4000,
        Shift = 0x0004,
        Win = 0x0008
    }

    // ******************************************************************
}
person Eric Ouellet    schedule 17.02.2012
comment
Я знаю, что прошло какое-то время, но когда я зарегистрирую свою кнопку PrintScreen, настоящий метод PrintScreen перестал работать. handled = true тоже не сработало. Какие-либо предложения? - person Erwin Okken; 02.06.2016
comment
@ErwinOkken, я не уверен, что правильно понял. HotKey является глобальным для вашей сессии (~ глобальным для вашей машины). Если вы зарегистрируете PrintScreen, то при его нажатии эта клавиша должна обрабатываться в вашей программе. Закрытие вашей программы должно позволить PrintScreen работать как обычно (перед запуском вашей программы). Вы имеете в виду, что не умеете обрабатывать (регистрировать) PrintScreen в своей программе? - person Eric Ouellet; 02.06.2016
comment
Извините, если я не понял. Функция PrintScreen не должна терять свою изначальную особенность: сохранение изображения в буфере обмена. Когда или после того, как это произойдет, мне нужно его зацепить. - person Erwin Okken; 03.06.2016
comment
@ErwinOkken, я не знаю. Вы можете посмотреть: msdn. microsoft.com/en-ca/library/windows/desktop/ и msdn.microsoft.com/en-ca/library/windows/desktop/. Я не могу найти информацию и всегда хватал ключ. Извини не знаю. - person Eric Ouellet; 03.06.2016
comment
Это должен быть настоящий ответ, поскольку он обеспечивает действительно глобальную горячую клавишу. Вопрос о том, является ли использование глобальной горячей клавиши в вашем приложении хорошей идеей, остается предметом обсуждения. - person Kohanz; 13.03.2017
comment
@ErwinOkken, слишком поздно (возможно, это решит вашу проблему) ... Но в ComponentDispatcherThreadFilterMessage вы не можете установить для параметра handled значение true, тогда также должно произойти поведение по умолчанию. Предлагаемый мной способ сделать это - переопределить новый фильтр с этим другим поведением и новым методом Register as RegisterWithDefaultBehavior и вручную вызвать этот метод вместо того, чтобы позволить конструктору сделать это за вас. - person Eric Ouellet; 18.05.2017

Регистрация ярлыков на уровне ОС вряд ли может быть хорошей вещью: пользователи не хотят, чтобы вы возились с их ОС.

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

В App.xaml.cs:

protected override void OnStartup(StartupEventArgs e)
{
   EventManager.RegisterClassHandler(typeof(Window), Window.PreviewKeyUpEvent, new KeyEventHandler(OnWindowKeyUp));
}

private void OnWindowKeyUp(object source, KeyEventArgs e))
{
   //Do whatever you like with e.Key and Keyboard.Modifiers
}

Это так просто

person Louis Kottmann    schedule 01.10.2012
comment
Он удаляется автоматически, когда вы закрываете приложение, в противном случае вам нужно отменить регистрацию в течение срока действия приложения. - person Louis Kottmann; 25.03.2013
comment
+1 за простоту и за указание на то, что ярлыки на уровне ОС плохие. Согласованный. - person Joel; 25.03.2013
comment
Мне это тоже нравится, интересно, есть ли уловка ... Для доступа к специальным клавишам, таким как control или shift, можно использовать if (e.Key == Key.U && e.KeyboardDevice.IsKeyDown (Key.LeftCtrl)) - person anhoppe; 16.07.2015
comment
ИМО, это должен быть принятый ответ из-за того, насколько он элегантен. - person Kredns; 23.03.2018

Если вы собираетесь смешивать Win32 и WPF, вот как я это сделал:

using System;
using System.Runtime.InteropServices;
using System.Windows.Interop;
using System.Windows.Media;
using System.Threading;
using System.Windows;
using System.Windows.Input;

namespace GlobalKeyboardHook
{
    public class KeyboardHandler : IDisposable
    {

        public const int WM_HOTKEY = 0x0312;
        public const int VIRTUALKEYCODE_FOR_CAPS_LOCK = 0x14;

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        private readonly Window _mainWindow;
        WindowInteropHelper _host;

        public KeyboardHandler(Window mainWindow)
        {
            _mainWindow = mainWindow;
            _host = new WindowInteropHelper(_mainWindow);

            SetupHotKey(_host.Handle);
            ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage;
        }

        void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled)
        {
            if (msg.message == WM_HOTKEY)
            {
                //Handle hot key kere
            }
        }

        private void SetupHotKey(IntPtr handle)
        {
            RegisterHotKey(handle, GetType().GetHashCode(), 0, VIRTUALKEYCODE_FOR_CAPS_LOCK);
        }

        public void Dispose()
        {
            UnregisterHotKey(_host.Handle, GetType().GetHashCode());
        }
    }
}

Вы можете получить код виртуальной клавиши для горячей клавиши, которую хотите зарегистрировать, здесь: http://msdn.microsoft.com/en-us/library/ms927178.aspx

Может быть способ получше, но это то, что у меня есть.

Ваше здоровье!

person jgraves    schedule 25.12.2009
comment
Как вы производите утечку памяти из этого кода? Его IDisposable - person user99999991; 14.08.2015
comment
ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage; Эта строка должна вызываться из потока пользовательского интерфейса, иначе вы не получите сообщения. - person Yusuf Tarık Günaydın; 14.09.2015
comment
Это также относится к функции RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc) - person Yusuf Tarık Günaydın; 14.09.2015

Это похоже на уже предоставленные ответы, но я считаю его немного чище:

using System;
using System.Windows.Forms;

namespace GlobalHotkeyExampleForm
{
    public partial class ExampleForm : Form
    {
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        enum KeyModifier
        {
            None = 0,
            Alt = 1,
            Control = 2,
            Shift = 4,
            WinKey = 8
        }

        public ExampleForm()
        {
            InitializeComponent();

            int id = 0;     // The id of the hotkey. 
            RegisterHotKey(this.Handle, id, (int)KeyModifier.Shift, Keys.A.GetHashCode());       // Register Shift + A as global hotkey. 
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (m.Msg == 0x0312)
            {
                /* Note that the three lines below are not needed if you only want to register one hotkey.
                 * The below lines are useful in case you want to register multiple keys, which you can use a switch with the id as argument, or if you want to know which key/modifier was pressed for some particular reason. */

                Keys key = (Keys)(((int)m.LParam >> 16) & 0xFFFF);                  // The key of the hotkey that was pressed.
                KeyModifier modifier = (KeyModifier)((int)m.LParam & 0xFFFF);       // The modifier of the hotkey that was pressed.
                int id = m.WParam.ToInt32();                                        // The id of the hotkey that was pressed.


                MessageBox.Show("Hotkey has been pressed!");
                // do something
            }
        }

        private void ExampleForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            UnregisterHotKey(this.Handle, 0);       // Unregister hotkey with id 0 before closing the form. You might want to call this more than once with different id values if you are planning to register more than one hotkey.
        }
    }
}

Я нашел его на fluxbytes.com.

person BornToCode    schedule 19.11.2015

Я не уверен насчет WPF, но это может помочь. Я использовал решение, описанное в RegisterHotKey (user32) < / em> (разумеется, модифицированный в соответствии с моими потребностями) для приложения Windows Forms на C #, чтобы назначить комбинацию CTRL-KEY в Windows для отображения формы C #, и это прекрасно работало (даже в Windows Vista). Надеюсь, это поможет и удачи!

person John Virgolino    schedule 08.09.2008
comment
Похоже, совсем не работает с клавишей Windows. Что ж, он поддерживает клавишу Windows, но любые горячие клавиши зарезервированы для самой Windows. - person erodewald; 10.04.2012

Я нашел проект Global Hotkeys в WPF на codeproject .com, который делает всю работу за меня. Это относительно недавно, не требует ссылки на System.Windows.Forms и работает «глобально» с точки зрения реакции на нажатие горячих клавиш, даже если «ваше» приложение не является активным окном.

person Gorgsenegger    schedule 14.06.2012

Решение Baboon работает лучше всего, потому что у вас может быть несколько окон. Я настроил его так, чтобы он использовал PreviewKeyDownEvent вместо PreviewKeyUpEvent, чтобы обрабатывать повторение нажатий клавиш.

Я бы посоветовал не регистрироваться на уровне ОС, если вы не пишете что-то вроде инструмента для обрезки или приложения для записи звука, поскольку оно позволит вам получить доступ к функциям, когда окно не сфокусировано.

person Mario    schedule 26.03.2013

Хотя RegisterHotKey иногда именно то, что вам нужно, в большинстве случаев вы, вероятно, не захотите использовать общесистемные горячие клавиши. В итоге я использовал такой код:

using System.Windows;
using System.Windows.Interop;

namespace WpfApp
{
    public partial class MainWindow : Window
    {
        const int WM_KEYUP = 0x0101;

        const int VK_RETURN = 0x0D;
        const int VK_LEFT = 0x25;  
      
        public MainWindow()
        {
            this.InitializeComponent();

            ComponentDispatcher.ThreadPreprocessMessage += 
                ComponentDispatcher_ThreadPreprocessMessage;
        }

        void ComponentDispatcher_ThreadPreprocessMessage(
            ref MSG msg, ref bool handled)
        {
            if (msg.message == WM_KEYUP)
            {
                if ((int)msg.wParam == VK_RETURN)
                    MessageBox.Show("RETURN was pressed");
                
                if ((int)msg.wParam == VK_LEFT)
                    MessageBox.Show("LEFT was pressed");
            }
        }
    }
}
person Mattias Wikström    schedule 15.04.2010

С пакетом NHotKey вы можете сделать свою горячую клавишу глобальной:

Короче говоря, для XAML все, что вам нужно сделать, это заменить

<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}" />

by

<KeyBinding Gesture="Ctrl+Alt+Add" Command="{Binding IncrementCommand}"
            HotkeyManager.RegisterGlobalHotkey="True" />
person Anthony Nguyen    schedule 11.09.2020

RegisterHotKey(), предложенный Джоном, может сработать - единственная загвоздка в том, что для этого требуется HWND (с использованием PresentationSource.FromVisual() и приведением результата к HwndSource).

Однако вам также необходимо ответить на сообщение WM_HOTKEY - я не уверен, есть ли способ получить доступ к WndProc окна WPF или нет (что можно сделать для окон Windows Forms).

person Andy    schedule 08.09.2008