介绍
有时,您的窗体或窗口(取决于您是否正在构建WinForm或WPF应用程序)可能需要响应热键或快捷方式(本文中可互换使用),无论应用程序是否集中或在后台运行。
此外,如果你想注册多个热键,在应用程序内或外,它有点困难。在本文中,我们将创建一个库,允许应用程序过程的热键和执行其他键盘操作。
术语
HotKeys:这些是用户在键盘上按下的键,可能需要由您的应用程序处理。
LocalHotKeys:一个在应用程序中管理常规热键的类;他们有一个键和或修饰符(修饰符是LocalHotkey的可选),它们只在应用程序集中时才工作。一个例子是Control + C(在大多数情况下用于复制)
GlobalHotKeys:在应用程序之外管理热键的类,无论窗体或窗口是否具有焦点或正在后台运行。它们是具有键AND修改器的常规热键,只要应用程序正在运行(无论是在后台聚焦,最小化还是运行),都会对其进行处理。一个例子是Window + L(用于锁定计算机)
ChordHotKeys:一类高级的LocalHotKeys,更像是两个LocalHotKeys; BASE和CHORD。基地必须有一个BaseKey AND一个BaseModifier,当它们被按下时,它们会启动Chord,Chord有一个键和一个可选的Modifier。一个例子是Control + K,Control + D(用于Visual Studio IDE中的缩进代码),在这种情况下,Control + K是基础,而Control + D是和弦的热键。像LocalHotKeys,他们只有当应用程序的重点工作。
HotKeyManager:这个类管理上面的类;它跟踪其更改,并在键盘上按下与其关联的热键时引发事件。此外,HotKeyManager类可以从键盘侦听所有键按下,报告给我们的应用程序,也可以模拟按键。
*注册热键意味着将Hotkey订阅到HotKeyManager。
HotKeyControl:允许用户通过输入设置热键的UserControl。
* HotKeyControl不会将Windows键视为修饰符,而是将其视为与Key(s)枚举中完全相同的键。
背景
在我开始使用.Net之前,我想禁用键盘上的一些键,特别是对于我创建的基于计算机的测试应用程序的Control + Alt + Delete,我研究了编程方式,但发现没有,直到我导致使用ScanCodeMap ,(现在不要太激动了,HotKeyManager不能这样做)只有警告是我不得不重新启动Windows,当我要启用或禁用这些键。我也有麻烦的.Net实现全局快捷键和使键可变。创建这个库是为了加快速度,允许您轻松地在键盘上执行操作,并在同一时间管理多个动态热键,如在VLC播放器和其他应用程序。
源文件
WPF的HotKeyManager类具有大多数WinForm等效项的功能,尽管它可能更容易在同一项目中为两个平台创建类,但是,如果您在WinForm中创建一个程序,并希望引用dll,你还需要导入:
PresentationCore
演示框架和
WindowsBase
这是WPF的核心库,并且如果你在WPF中创建一个程序,你将需要导入:
System.Windows.Forms和
System.Drawing。
版本取决于在编写项目时使用的版本,这将使您的项目被不必要的引用阻塞,这就是为什么我在两个单独的项目中编写库,保持代码使用完全相同。源文件(在C#中但与VB兼容)包含5个项目:C#2.0中的WinForm库,C#3.0和4.0中的WPF库(这只是因为我想为WPF创建一个更好的控件)项目一个为WinForm和WPF。
*我正在为WinForm创建另一个库,将类转换为组件,以便可以在设计时添加和验证,而不是在代码中编写它们。
上班。
快捷方式包含修饰符(可以是一个或多个)和单个键。修饰符键是:Shift键,Alt键,控制键和窗口键(这不完全是一个修饰符,应该只在创建GlobalShortcuts时使用),请注意,这是无论是左还是右键,WinForm中的右Alt键发送“Control + Alt”,WPF发送LeftCtrl,但是有一些调整发送与WinForm相同的键。
一个引用被添加到Microsoft.VisualBasic,我们将使用它来执行一些字符串操作和获得一些键盘键状态。
我们定义一个修饰符枚举,并用flags属性标记它,该属性指定枚举可以被视为一个位字段,第二个枚举定义了当我们想要我们的本地热键事件时。
//首先,我们要定义GlobalHotkeys和LocalHotKeys的修饰符。
#region **修饰符和常量。
/// <summary>定义用作“修改器”的键。
/// </summary>
[Flags]
public enum Modifiers
{
/// <summary>指定键应被视为是,不带任何修饰符。
/// </summary>
NoModifier = 0x0000,
/// <summary>指定用键按下加速键(ALT)。
/// </summary>
Alt = 0x0001,
/// <summary>指定用键按下控制键。
/// </summary>
Ctrl = 0x0002,
/// <summary>指定使用相关键按下Shift键。
/// </summary>
Shift = 0x0004,
/// <summary>指定使用相关键按下Window键。
/// </summary>
Win = 0x0008
}
public enum RaiseLocalEvent
{
OnKeyDown = 0x100, //也是256.和WM_KEYDOWN相同。
OnKeyUp = 0x101 //另外257,与WM_KEYUP相同。
}
#endregion
WPF已经在命名空间System.Windows.Input中有自己的修饰符键作为ModifierKeys,并且还设置了flags属性,因此这不是必需的。
flags属性允许修饰符通过XOR组合,所以可以在WinForm中写一个语句:
**In C#**
Modifiers modifier = Modifiers.Control | Modifiers.Shift;
**In VB**
Dim modifier as Modifiers = Modifiers.Control Xor Modifiers.Shift
**And in WPF,**
ModifierKeys modifier = ModifierKeys.Control | ModifierKeys.Shift;
**In VB**
Dim modifier as ModifierKeys = ModifierKeys.Control Xor ModifierKeys.Shift
意思是修饰符是“Control + Shift”
RaiseLocalEvent枚举将确定何时应该引发LocalHotKey事件,当键被关闭(OnKeyDown)或当它被释放(OnKeyUp)时。
public class HotKeyAlreadyRegisteredException : Exception
public class HotKeyUnregistrationFailedException : Exception
public class HotKeyRegistrationFailedException : Exception
public class HotKeyInvalidNameException : Exception
HotKeyAlreadyRegisteredException:顾名思义,当尝试使用HotKeyManager重新注册热键(具有相同的名称,键和或修饰符)时,将抛出此异常。对于GlobalHotKeys,当全局热键的键和修饰符正被另一个应用程序使用时,会抛出此异常。例如,尝试注册Window + L会引发此异常。当使用库时,尝试用LocalHotKey正在使用的基本键和基本修饰符注册ChordHotKey会引发HotKeyAlreadyRegisteredException,同时尝试注册具有已注册为基本键和修饰符的键和修饰符的LocalHotKey的ChordHotKey会引发相同的异常。优先级给予首次注册的HotKey。
HotKeyUnregistrationFailedException:当HotKey无法注销时抛出此异常。 HotKeyRegistrationFailedException相同,当HotKey无法注册时抛出,也会在您试图注册HotKeys(如Control + Escape)时发生。
HotKeyInvalidNameException:当您尝试注册具有无效名称的HotKey时抛出此异常;此库中的热键更像控件,您需要为每个热键分配一个名称,与Visual Studio中相同,有效的HotKey名称不以数字开头或包含空格。在函数中检查名称。
public static bool IsValidHotkeyName(string text)
{
//If the name starts with a number, contains space or is null, return false.
if (string.IsNullOrEmpty(text)) return false;
if (text.Contains(" ") || char.IsDigit((char)text.ToCharArray().GetValue(0)))
return false;
return true;
}
当然,如果你喜欢,你可以改变这个。
HotKey共享类
这是一个静态类,有助于执行一些功能,如检查前面讨论的HotKey控件的名称,将字符串拆分为其相应的键和修饰符(对我们的HotKeyControl有用)和逆转过程。 它还包含一个枚举修饰符的结构。 HotKeyControl将把热键作为一个字符串,这不是非常有用的,除非我们将其分割为各自的键和修饰符,ParseShortcut函数允许我们实现这一点。
类具有静态函数ParseShortcut和CombineShortcut。 前者允许你剥离一个快捷方式,说“Control + Alt + T”到它各自的修饰符(Control,Alt)和键(T),而后者做相反。
ParseShortcut函数是一个对象数组,返回其下限中的热键字符串的修饰符,以及上限中的键。
函数public static object [] ParseShortcut(string text)有一个重载。
public static object[] ParseShortcut(string text, string separator)
{
bool HasAlt = false; bool HasControl = false; bool HasShift = false; bool HasWin = false;
Modifiers Modifier = Modifiers.None; //变量包含修饰符。
Keys key = 0; //注册的键。
string[] result;
string[] separators = new string[] { separator };
result = text.Split(separators, StringSplitOptions.RemoveEmptyEntries);
//迭代通过键,找到修饰符。
foreach (string entry in result)
{
//Find the Control Key.
if (entry.Trim() == Keys.Control.ToString())
{
HasControl = true;
}
//Find the Alt key.
if (entry.Trim() == Keys.Alt.ToString())
{
HasAlt = true;
}
//Find the Shift key.
if (entry.Trim() == Keys.Shift.ToString())
{
HasShift = true;
}
//Find the Window key.
if (entry.Trim() == Keys.LWin.ToString())
{
HasWin = true;
}
}
if (HasControl) { Modifier |= Modifiers.Control; }
if (HasAlt) { Modifier |= Modifiers.Alt; }
if (HasShift) { Modifier |= Modifiers.Shift; }
if (HasWin) { Modifier |= Modifiers.Win; }
KeysConverter keyconverter = new KeysConverter();
key = (Keys)keyconverter.ConvertFrom(result.GetValue(result.Length - 1));
return new object[] { Modifier, key };
}
请注意,该函数使用KeysConverter类,System.Windows.Forms.Keys枚举的TypeConverter将字符串转换为其Key枚举表示形式。
用法:
**In C#**
object[] Result = ParseShortcut(“Control + Shift + A”, “ + ”);
Modifiers modifier = (Modifiers)Result[0]; //Control | Shift
Keys key = (Keys)Result[1]; //Keys.A
**In VB**
Dim Result() as Object = ParseShortcut(“Control + Shift + A”, “ + “)
Dim modifier as Modifiers = CType(Result(0), Modifiers) 'Control Xor Shift
Dim key as Keys = CType(Result(0), Keys) 'Keys.A
And to reverse this process, we use the CombineShortcut function
public static string CombineShortcut(Modifiers mod, Keys key)
{
string hotkey = "";
foreach (Modifiers a in new HotKeyShared.ParseModifier((int)mod))
{
hotkey += a.ToString() + " + ";
}
if (hotkey.Contains(Modifiers.None.ToString())) hotkey = "";
hotkey += key.ToString();
return hotkey;
}
**Usage:**
Modifiers modifier = Modifiers.Control | Modifiers.Shift;
CombineShortcut(modifier, Keys.A); //Control + Shift + A
Dim modifier as Modifiers = Modifiers.Control Xor Modifiers.Shift
CombineShortcut(modifier, Keys.A) 'Control + Shift + A
热键控制
HotKeyControl是一个UserControl,它扩展了TextBox控件以捕捉用户在活动时按下的键。 它添加了当用户设置HotKey时引发的事件HotKeyIsSet,并且还添加了属性UserKey和UserModifer(在设计视图中不可见),它返回键和修改用户集和属性ForceModifiers(在设计视图中可见) ),其指定当设置热键时用户应该被强制输入修改,如果为真,则接受所有热键。
对于WinForm
HotKey控件利用文本框的KeyDown和KeyUp事件来获得用户按下的键。 并使用复位按钮清除输入的HotKey。
void HotKeyControl_KeyDown(object sender, KeyEventArgs e)
{
e.SuppressKeyPress = true; //禁止键由底层控件处理。
this.Text = string.Empty; //清空文本框的内容
KeyisSet = false; //此时用户尚未指定快捷方式。
//使用户指定修饰符。 控制,Alt或Shift。
//如果不存在修饰符,请清除文本框。
if (e.Modifiers == Keys.None && forcemodifier)
{
MessageBox.Show("You have to specify a modifier like 'Control', 'Alt' or 'Shift'");
this.Text = Keys.None.ToString();
return;
}
//A modifier is present. Process each modifier.存在改性剂。 处理每个修饰符。
//修饰符由“,”分隔。 所以我们将它们分开,并将每一个写入文本框。
foreach (string modifier in e.Modifiers.ToString().Split(new Char[] { ',' }))
{
if (modifier != Keys.None.ToString())
this.Text += modifier + " + ";
}
//KEYCODE包含用户按下的最后一个键。
//如果KEYCODE包含修饰符,则用户没有输入快捷方式。 因此,KeyisSet为假
//But if not, KeyisSet is true.但如果不是,KeyisSet是真的。
if (e.KeyCode == Keys.ShiftKey | e.KeyCode == Keys.ControlKey | e.KeyCode == Keys.Menu)
{
KeyisSet = false;
}
else
{
this.Text += e.KeyCode.ToString();
KeyisSet = true;
}
}
KeyUp事件通过检查变量KeyisSet来确定HotKey是否已经设置,如果true提高HotKeyIsSet事件或清除控件。
void HotKeyControl_KeyUp(object sender, KeyEventArgs e)
{
//在KeyUp如果KeyisSet为False然后清除文本框。
if (KeyisSet == false)
{
this.Text = Keys.None.ToString();
}
else
{
if (HotKeyIsSet != null)
{
var ex = new HotKeyIsSetEventArgs(UserKey, UserModifier);
HotKeyIsSet(this, ex);
if (ex.Cancel)
{
KeyisSet = false;
this.Text = Keys.None.ToString();
}
}
}
}
对于WPF
源文件包含两个HotKey控件,一个构建在.Net Framework 3.0中,另一个构建在.Net Framework 4.0中。
HotKeyControl使用PreviewKeyDown事件和一个钩子来获取用户按下的键。
public HotKeyControl()
{
this.GotFocus += new RoutedEventHandler(HotKeyControl_GotFocus); //在这里挂钩。
this.hook = new HwndSourceHook(WndProc); //接到Windows消息。
this.LostFocus += new RoutedEventHandler(HotKeyControl_LostFocus); //这里取下挂钩。
this.ContextMenu = null; //禁用快捷方式。
Text = Keys.None.ToString();
this.IsReadOnly = true;
this.PreviewKeyDown += new KeyEventHandler(HotKeyControl_PreviewKeyDown);
}
- HotKey必须在HotKeyManager中注册,然后才能工作(引发事件)
HotKeyManager
在WinForm中的HotKeyManager类将管理GlobalHotKey,LocalHotKey和ChordHotKey类,实现:
IMessageFilter:将允许类接收Windows消息回调。
IDisposable:将释放所有资源,并注销所有HotKeys。
该类将通过添加接收Windows消息
Application.AddMessageFilter(this);
在构造函数中,将通过添加停止接收Windows消息
Application.RemoveMessageFilter(this);
在析构函数中。
然后类将通过添加函数接收消息,这是实现IMessageFilter的结果。
public bool PreFilterMessage(ref Message m) { }
对于WPF,一个Hook被添加到类,以允许它接收Windows消息。
我将Key枚举从WinForm复制到WPF类,以允许Local和ChordHotKeys,因为Windows仍然向WinForm发送与WPF应用程序相同的密钥消息。
this.hook = new HwndSourceHook(WndProc); //接到Windows消息。
this.hwndSource = (HwndSource)HwndSource.FromVisual(window); // new WindowInteropHelper(window).Handle // 如果需要InPtr。
this.hwndSource.AddHook(hook);
然后类将从函数接收Windows消息。
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { }
在源文件中,HotKeyManager没有空导航,这是因为我想要您提供一个窗体或窗口,视情况而定,因为GlobalHotKeys注册的窗体或窗口的句柄是必需的Windows注册或取消注册。如果你想在服务中使用这个库,或者你不打算使用GlobalHotKeys,你可以添加一个空的构造函数并进行必要的修改,库仍然可以正常工作。
HotKeyManager在注册的窗体或窗口正在关闭时自动处理,您不需要在您的应用程序中重新构建,并且如果您这样做,则不会抛出异常。它也可以被禁用,它仍然接收消息,它只是不做任何事情与他们和在WinForm中,你可以设置HotKeyManager临时禁用自己,当另一个表单,可能是一个对话框,因为它仍然引发事件,设置DisableOnManagerFormInactive到真实。
现在,当您的应用程序中按下一个键时,Windows会发送一条消息,其中包含有关按下的键和状态(无论是按住还是已发布)的信息到我们的类,因为我们已订阅它们。
但是,为了在按下按键时按下Modifiers,我引用了Microsoft.VisualBasic来加快速度。你可以随时在键盘上得到修改器,如下所示:
Microsoft.VisualBasic.Devices.Keyboard UserKeyBoard = new Microsoft.VisualBasic.Devices.Keyboard();
bool AltPressed = UserKeyBoard.AltKeyDown;
bool ControlPressed = UserKeyBoard.CtrlKeyDown;
bool ShiftPressed = UserKeyBoard.ShiftKeyDown;
您可以看到Window键不作为修饰符而是作为Key,要获得Window键的状态,可以使用:
short ret = Win32.GetKeyState(0x5b); //Get the state of the Window key.
if ((ret & 0x8000) == 0x8000) LocalModifier |= Modifiers.Window;
当在键盘上按任何键时,HotKeyManager遍历所有注册的LocalHotKeys和ChordHotKeys,并为发现的事件引发事件。
查找LocalHotKeys
Using delegates
Keys keydownCode = (Keys)(int)m.WParam & Keys.KeyCode; //获取被按下的键。
LocalHotKey KeyDownHotkey = LocalHotKeyContainer.Find
(
delegate(LocalHotKey d)
{
return ((d.Key == keydownCode) && (d.Modifier == LocalModifier)
&& (d.WhenToRaise == RaiseLocalEvent.OnKeyDown));
}
);
using Linq,
LocalHotKey KeyDownHotkey = (from items in LocalHotKeyContainer
where items.Key == keydownCode && items.Modifier == LocalModifier
where items.WhenToRaise == RaiseLocalEvent.OnKeyDown
select items).FirstOrDefault();
*当没有找到任何内容时,此搜索返回null。
查找ChordHotKeys
当无法找到LocalHotKey时,Manager会检查任何注册的ChordHotKey的基本键和修饰符是否与按下的键匹配,这就是为什么您不能为任何其他localhotkey设置相同的基本键和修饰符。 如果找到一个,则它转到ChordMode,并等待另一个键被丢弃修改符,如果被按下的第二个键匹配任何和弦的chordkey和修改符,则它提出该和弦的事件,否则叶子ChordMode并给出 听起来像Visual Studio。
查找GlobalHotKeys
要注册GlobalHotKeys,没有内置的功能允许这种功能,但它是内置在Win32 API和.Net提供了一种方法来调用非本地库。 我们感兴趣的方法在User32.dll,RegisterHotKey和UnRegisterHotKey中定义。
RegisterHotKey和UnregisterHotKey。
现在,定义重要的方法:允许我们注册快捷方式的静态函数。
[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int RegisterHotKey(IntPtr hwnd, int id, int modifiers, int key);
[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern int UnregisterHotKey(IntPtr hwnd, int id);
请注意,我们不提供方法体。 该方法在user32.dll中定义,我们只是为我们的应用程序添加一种方法来直接调用该方法。
1.hWnd是指窗体或窗口句柄,
2.id是热键的唯一标识符。
3.修饰符是要用您的键按下的修饰键(shift / alt / ctrl / win)的整数表示,
4.而key是热键的虚拟键代码。
当按下GlobalHotKey时,Windows会在发送注册到wParam时分配给它的GlobalHotKey的ID,然后该ID可用于搜索所有GlobalHotKey,就像我们对LocalHotKeys所做的那样。
其他功能
HotKeyManager也支持枚举,可以迭代一个特定的热键; 全局,本地或和弦这样:
string message = "Global HotKeys.\n";
foreach (GlobalHotKey gh in MyHotKeyManager.EnumerateGlobalHotKeys)
{
message += string.Format("{0}{1}", Environment.NewLine, gh.FullInfo());
}
message += "\n\nLocal HotKeys.\n";
foreach (LocalHotKey lh in MyHotKeyManager.EnumerateLocalHotKeys)
{
message += string.Format("{0}{1}", Environment.NewLine, lh.FullInfo());
}
message += "\n\nChord HotKeys.\n";
foreach (ChordHotKey ch in MyHotKeyManager.EnumerateChordHotKeys)
{
message += string.Format("{0}{1}", Environment.NewLine, ch.FullInfo());
}
MessageBox.Show(message, "All HotKeys registered by this app.", MessageBoxButtons.OK, MessageBoxIcon.Information);
这将允许迭代更容易,因为我们可以直接遍历所有ChordHotKeys像这样
而不是像这样实现IEnumerable 和迭代,这将需要使用命名空间System.Collections.Generic
foreach (ChordHotKey ch in (IEnumerable<ChordHotKey>)MyHotKeyManager)
{
message += string.Format("{0}{1}", Environment.NewLine, gh.FullInfo());
}
HotKeyManager还利用Win32 API获取所有键盘按下,即使您的应用程序不专注,可以禁用它们。
KeyboardHookEventHandler keyboardhandler = (sender, handler) =>
{
if (handler.Modifier == KeyboardHookEventArgs.modifiers.Shift)
{ handler.Handled = true; }
switch (handler.Key)
{
case Keys.A:
case Keys.E:
case Keys.I:
case Keys.O:
case Keys.U:
handler.Handled = true;
return;
}
};
上面的代码禁用键盘上的元音键,但按下Shift键时会禁用所有键。
HotKeyManager,使用Win32API也可以模拟一个Key按,在这里,我们模拟按Control + A。
MyHotKeyManager.SimulateKeyDown(Keys.Control); //按住控制键
MyHotKeyManager.SimulateKeyPress(Keys.A); //按住 A 键
MyHotKeyManager.SimulateKeyUp(Keys.Control); //释放控制键
HotKey管理器是通过取消注册所有注册的GlobalHotKeys注册的类,从所有键盘消息从方法中挂钩类,HotKeyManager.Dispose
for (int i = GlobalHotKeyContainer.Count - 1; i >= 0; i--)
{
RemoveGlobalHotKey(GlobalHotKeyContainer[i]);
}
LocalHotKeyContainer.Clear();
ChordHotKeyContainer.Clear();
KeyBoardUnHook();
使用代码
现在,我们将HotKeys添加到HotKeyManager中,如下所示:
**In C#**
GlobalHotKey ghkNotepad = new GlobalHotKey("ghkNotepad", Keys.N, Modifiers.Control | Modifiers.Shift);
LocalHotKey lhkNewHotkey = new LocalHotKey("lhkNewHotKey", Keys.A);
ChordHotKey chotCmd = new ChordHotKey("chotCmd", Keys.C, Modifiers.Alt, Keys.P, Modifiers.Alt);
MyHotKeyManager.AddGlobalHotKey(ghkNotepad);
MyHotKeyManager.AddLocalHotKey(lhkNewHotkey);
MyHotKeyManager.AddChordHotKey(chotCmd);
**In VB**
Dim ghkNotepad as new GlobalHotKey("ghkNotepad", Keys.N, Modifiers.Control Xor Modifiers.Shift)
Dim lhkNewHotkey as new LocalHotKey("lhkNewHotKey", Keys.A)
Dim chotCmd as new ChordHotKey("chotCmd", Keys.C, Modifiers.Alt, Keys.P, Modifiers.Alt)
MyHotKeyManager.AddGlobalHotKey(ghkNotepad)
MyHotKeyManager.AddLocalHotKey(lhkNewHotKey)
MyHotKeyManager.AddChordHotKey(chotCmd)
对于HotKeyControl,用户可以使用控件设置快捷方式,然后可以从HotKeyControl.UserKey和HotKeyControl.UserModifier获取关联的键和修饰符。
HotKeyManager还可以模拟按键并向键盘添加一个挂钩。
您可以通过将键的Handled属性设置为true来禁用键盘键,似乎被忽略的键的唯一组合是Control + Alt + Delete。
当你尝试创建一个新的HotKeyManager,在InitializeComponent方法中使用WPF,它可能会导致你的窗口崩溃,你最好这样做时,窗口已经加载。
库还可以扩展到组件中,以便可以在设计时和运行时添加和验证HotKey,它将为您节省大量代码。
源文件中的示例如下所示。
翻译来源:https://www.codeproject.com/articles/442285/global-shortcuts-in-winforms-and-wpf