Глобальные горячие клавиши
Пишу для себя програмку для упрощения работы. Суть програмы - запуститься, свернуться в трей и слушать клавиатуру на предмет комбинации клавиш
И вот вылез баг: нужно чтоб при нажатии комбинации клавиш остальная система игнорировала её например: нажал я ctrl+n, программа отработала, но система реагирует на нажатие n, вводя её в, например, строку браузера, или открывая новое окно
e.handled = true - не помогло, так как блочит любое нажатие ctrl или n
пробую e.handled по условию - вообще магия - ctrl изменяет поведение, а вот n продолжает работать опыты показали, что, похоже, моя программа далеко не первая в очереди обработчиков нажатия n
Честно говоря, я затрудняюсь объяснить, как именно себя ведёт алгоритм, так как тестировал миллион вариантов расположения e.handled = true и уже запутался, но вот для примера:
e.handled = true, расположенный без условий в самом начале обработчика отлично выключает ctrl и n по всей системе
e.handled = true при условии нажатия именно n и всех предыдущих клавиш попросту не работает, как вот здесь:
private void gkh_KeyDown(object sender, KeyEventArgs e)
{
//e.Handled = true;
if (_combinaison.IndexOf(e.KeyCode) == _combinaison.Count - 1)
{
bool before_pressed = true;
for (int i = 0; i < _combinaison.IndexOf(e.KeyCode); i++)
{
if (_pressed[i] == false)
{
before_pressed = false;
}
}
if (before_pressed == true)
{
e.Handled = true;
_f(_combinaison);
}
}
else
{
_pressed[_combinaison.IndexOf(e.KeyCode)] = true;
}
}
Вот мой код:
class Combinaison
{
public delegate void EventFunction(List<Keys> keys);
private globalKeyboardHook _gkh = new globalKeyboardHook();
private EventFunction _f;
private List<Keys> _combinaison;
private List<bool> _pressed = new List<bool>();
public Combinaison(List<Keys> combinaison, EventFunction f)
{
_f = f;
_combinaison = combinaison;
foreach (Keys k in _combinaison)
{
_gkh.HookedKeys.Add(k);
_pressed.Add(false);
}
_gkh.KeyDown += new KeyEventHandler(gkh_KeyDown);
_gkh.KeyUp += new KeyEventHandler(gkh_KeyUp);
}
private void gkh_KeyUp(object sender, KeyEventArgs e)
{
if (_combinaison.IndexOf(e.KeyCode) == _combinaison.Count - 1)
{
e.Handled = true;
}
_pressed[_combinaison.IndexOf(e.KeyCode)] = false;
}
private void gkh_KeyDown(object sender, KeyEventArgs e)
{
if (_combinaison.IndexOf(e.KeyCode) == _combinaison.Count - 1)
{
bool before_pressed = true;
for (int i = 0; i < _combinaison.IndexOf(e.KeyCode); i++)
{
if (_pressed[i] == false)
{
before_pressed = false;
}
}
if (before_pressed == true)
{
_f(_combinaison);
}
}
else
{
_pressed[_combinaison.IndexOf(e.KeyCode)] = true;
}
}
}
class globalKeyboardHook
{
#region Constant, Structure and Delegate Definitions
/// <summary>
/// defines the callback type for the hook
/// </summary>
public delegate int keyboardHookProc(int code, int wParam, ref keyboardHookStruct lParam);
public struct keyboardHookStruct
{
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}
const int WH_KEYBOARD_LL = 13;
const int WM_KEYDOWN = 0x100;
const int WM_KEYUP = 0x101;
const int WM_SYSKEYDOWN = 0x104;
const int WM_SYSKEYUP = 0x105;
#endregion
#region Instance Variables
/// <summary>
/// The collections of keys to watch for
/// </summary>
public List<Keys> HookedKeys = new List<Keys>();
/// <summary>
/// Handle to the hook, need this to unhook and call the next hook
/// </summary>
IntPtr hhook = IntPtr.Zero;
#endregion
#region Events
/// <summary>
/// Occurs when one of the hooked keys is pressed
/// </summary>
public event KeyEventHandler KeyDown;
/// <summary>
/// Occurs when one of the hooked keys is released
/// </summary>
public event KeyEventHandler KeyUp;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="globalKeyboardHook"/> class and installs the keyboard hook.
/// </summary>
public globalKeyboardHook()
{
hook();
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations before the
/// <see cref="globalKeyboardHook"/> is reclaimed by garbage collection and uninstalls the keyboard hook.
/// </summary>
~globalKeyboardHook()
{
unhook();
}
#endregion
#region Public Methods
/// <summary>
/// Installs the global hook
/// </summary>
public void hook()
{
IntPtr hInstance = LoadLibrary("User32");
hhook = SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, hInstance, 0);
}
/// <summary>
/// Uninstalls the global hook
/// </summary>
public void unhook()
{
UnhookWindowsHookEx(hhook);
}
/// <summary>
/// The callback for the keyboard hook
/// </summary>
/// <param name="code">The hook code, if it isn't >= 0, the function shouldn't do anyting</param>
/// <param name="wParam">The event type</param>
/// <param name="lParam">The keyhook event information</param>
/// <returns></returns>
public int hookProc(int code, int wParam, ref keyboardHookStruct lParam)
{
if (code >= 0)
{
Keys key = (Keys)lParam.vkCode;
if (HookedKeys.Contains(key))
{
KeyEventArgs kea = new KeyEventArgs(key);
if ((wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) && (KeyDown != null))
{
KeyDown(this, kea);
}
else if ((wParam == WM_KEYUP || wParam == WM_SYSKEYUP) && (KeyUp != null))
{
KeyUp(this, kea);
}
if (kea.Handled)
return 1;
}
}
return CallNextHookEx(hhook, code, wParam, ref lParam);
}
#endregion
#region DLL imports
/// <summary>
/// Sets the windows hook, do the desired event, one of hInstance or threadId must be non-null
/// </summary>
/// <param name="idHook">The id of the event you want to hook</param>
/// <param name="callback">The callback.</param>
/// <param name="hInstance">The handle you want to attach the event to, can be null</param>
/// <param name="threadId">The thread you want to attach the event to, can be null</param>
/// <returns>a handle to the desired hook</returns>
[DllImport("user32.dll")]
static extern IntPtr SetWindowsHookEx(int idHook, keyboardHookProc callback, IntPtr hInstance, uint threadId);
/// <summary>
/// Unhooks the windows hook.
/// </summary>
/// <param name="hInstance">The hook handle that was returned from SetWindowsHookEx</param>
/// <returns>True if successful, false otherwise</returns>
[DllImport("user32.dll")]
static extern bool UnhookWindowsHookEx(IntPtr hInstance);
/// <summary>
/// Calls the next hook.
/// </summary>
/// <param name="idHook">The hook id</param>
/// <param name="nCode">The hook code</param>
/// <param name="wParam">The wparam.</param>
/// <param name="lParam">The lparam.</param>
/// <returns></returns>
[DllImport("user32.dll")]
static extern int CallNextHookEx(IntPtr idHook, int nCode, int wParam, ref keyboardHookStruct lParam);
/// <summary>
/// Loads the library.
/// </summary>
/// <param name="lpFileName">Name of the library</param>
/// <returns>A handle to the library</returns>
[DllImport("kernel32.dll")]
static extern IntPtr LoadLibrary(string lpFileName);
#endregion
}
Ответы (1 шт):
Вам нужна регистрация глобальных горячих клавиш в системе, тогда они будут перехватываться, а не прослушиваться.
Вот, нашел в старых архивах такой класс.
Прошу прощения, класс не оптимизирован но это я писал почти 7 лет назад собирая инфу по англоязычному интернету. Потом проект был переписан под WPF, а это просто архивная копия того, что было под Winforms. Сейчас я бы не стал регистрировать каждый раз MessageFilter на каждую горячую клавишу, а зарегистрировал бы один, и он бы ловил все (наверное, надо тестировать). Но вы можете проверить и доработать.
public sealed class HotKey : IMessageFilter, IDisposable
{
public event KeyEventHandler HotKeyPressed;
public event EventHandler KeyChanged;
public event EventHandler KeyModifierChanged;
private readonly int _id;
#region Native win32 API
private const int WM_HOTKEY = 0x0312;
[DllImport("user32.dll", SetLastError = true)]
private static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
[Flags]
public enum KeyModifiers
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
#endregion
public IntPtr Handle { get; set; }
private Keys _key = Keys.None;
private KeyModifiers _keyModifier;
public bool isKeyRegistered = false;
public HotKey(IntPtr handle)
{
Handle = handle;
_id = GetHashCode();
Application.AddMessageFilter(this);
}
public void Dispose()
{
Application.RemoveMessageFilter(this);
if (isKeyRegistered && !UnregisterHotKey(Handle, _id))
throw new ApplicationException("Failed to unregister hotkey");
isKeyRegistered = false;
}
public void RegisterHotKey()
{
if (_key == Keys.None || _keyModifier == KeyModifiers.None)
{
return;
}
if (isKeyRegistered && !UnregisterHotKey(Handle, _id))
throw new ApplicationException("Failed to unregister hotkey");
if (!RegisterHotKey(Handle, _id, _keyModifier, _key))
throw new ApplicationException("Failed to register hotkey");
isKeyRegistered = true;
}
[Bindable(true), Category("HotKey")]
public Keys Key
{
get { return _key; }
set
{
if (_key != value)
{
_key = value;
OnKeyChanged(new EventArgs());
}
}
}
[Bindable(true), Category("HotKey")]
public KeyModifiers KeyModifier
{
get { return _keyModifier; }
set
{
if (_keyModifier != value)
{
_keyModifier = value;
OnKeyModifierChanged(new EventArgs());
}
}
}
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
public bool PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_HOTKEY:
if ((int)m.WParam == _id)
{
KeyEventArgs args = new KeyEventArgs((IsButtonDown(m.LParam, KeyModifiers.Alt) ? Keys.Alt : Keys.None)
| (IsButtonDown(m.LParam, KeyModifiers.Control) ? Keys.Control : Keys.None)
| (IsButtonDown(m.LParam, KeyModifiers.Shift) ? Keys.Shift : Keys.None) | Key);
OnHotKeyPressed(args);
return true;
}
break;
}
return false;
}
private static bool IsButtonDown(IntPtr ptr, KeyModifiers keyModifiers)
{
return Convert.ToBoolean(((long)ptr) & (long)keyModifiers);
}
private void OnHotKeyPressed(KeyEventArgs e)
{
HotKeyPressed?.Invoke(this, e);
}
private void OnKeyChanged(EventArgs e)
{
RegisterHotKey();
KeyChanged?.Invoke(this, e);
}
private void OnKeyModifierChanged(EventArgs e)
{
RegisterHotKey();
KeyModifierChanged?.Invoke(this, e);
}
}
Использование
public HotKey RegisterHotKey(Keys key, HotKey.KeyModifiers modifiers)
{
HotKey hk = new HotKey(this.Handle);
hk.KeyModifier = modifiers;
hk.Key = key;
hk.HotKeyPressed += new KeyEventHandler(KeyHandler);
if (!hk.isKeyRegistered)
{
hk.Dispose();
hk = null;
}
return hk;
}
public void UnRegisterHotkey(HotKey hotkey)
{
hotkey?.Dispose();
}
private void KeyHandler(object sender, KeyEventArgs e)
{
string hotkey = e.Control ? "Ctrl+" : "";
hotkey += e.Alt ? "Alt+" : "";
hotkey += e.Shift ? "Shift+" : "";
hotkey += e.KeyValue >= 28 && e.KeyValue <= 57 ? e.KeyCode.ToString().Substring(1) : e.KeyCode.ToString();
MessageBox.Show(hotkey); // покажет, что нажалось
}
Регистрация
HotKey h = RegisterHotKey(Keys.D1, HotKey.KeyModifiers.Control); // Crtl + 1
Отмена регистрации
UnRegisterHotkey(h);