C#.NET Winform 注冊使用全域快捷鍵詳解
借助于全域快捷鍵,用戶可以在任何地方操控程式,觸發對應的功能,但 WinForms 框架并沒有提供全域快捷鍵的功能,想要實作全域快捷鍵需要跟 Windows API 打交道,本文就交你如何使用 Windows API 使用全域快捷鍵,
了解訊息回圈機制
訊息機制簡要介紹
一個表單到底是如何作業的呢?它是如何回應用戶的操作的呢?不妨先讓我們搞明白一個程式的運行機制吧,
在 Windows 上面,一個桌面應用程式是通過訊息機制驅動的,訊息(Message)攜帶著對應表單發生了什么的資訊,如,用戶按下了按鍵、滑鼠移動或者點擊等等,
那么作業流程是怎樣的呢?
首先,用戶做出了一些操作或者一些其他的事情發生了,系統就會創建一條訊息出來,接著,把訊息投送到當前對應的表單的執行緒訊息佇列,等待應用程式處理訊息,訊息會攜帶一個表單的句柄、一個訊息號、以及一些額外資訊,這些資訊可以告訴應用程式,到底發生了什么事情,
應用程式完成初始化之后,就開始建立訊息處理機制,通過不斷回圈從訊息佇列獲取訊息,對于那些有對應目標表單的訊息,將訊息轉發到對應表單的表單處理函式,
表單處理函式負責處理訊息,
在 Win Forms 中,訊息的派發機制
在 Win Forms 中, Application.Run 方法就實作了訊息處理機制,我們看一下 Program.cs 中的以下代碼,這段代碼就是創建一個表單,接著,把表單傳入 Application.Run 方法,而 Application.Run 方法,首先顯示這個表單,接著就開始回圈從訊息佇列獲取訊息并派發訊息了,
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new CRForm());
}
Application.Run 方法的描述如下:
在當前執行緒上開始運行標準應用程式訊息回圈,并使指定表單可見,
那么,能不能直觀的看到有哪些訊息放到了咱們的訊息佇列里面呢?通過查看 Application 類的檔案,我們找到了如下方法:
public static void AddMessageFilter (System.Windows.Forms.IMessageFilter value);
添加訊息篩選器以便在向目標傳送 Windows 訊息時監視這些訊息,
很顯然,想要查看到訊息需要我們實作一個 IMessageFilter 介面的類,我們來撰寫一個這樣的類:如下:
internal class MyMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
Console.WriteLine("MyMessageFilter: {0}", m.ToString());
return false;
}
}
代碼非常的易懂,不過值得說到的是,回傳 false 的含義是允許這條訊息繼續向下傳遞,如果回傳 true,則該條訊息就不會往下繼續傳遞,
下面,我們把這個訊息處理器注冊到 Application 中去,
Main 方法下撰寫如下的代碼:
Application.AddMessageFilter(new MyMessageFilter());
Application.Run(new CRForm());
第一行就是我們新增加的代碼,接著為了能出現控制臺視窗,我們應該把程式的目標平臺選為 Windows 控制臺程式,最后開始執行應用程式,應該就可以在控制臺中看到有資訊輸出了,
表單的訊息處理函式探秘
通過 Application 建立的訊息派發機制,訊息會被發送到下一站,也就是表單的訊息處理函式,在 Win Forms 中,我們可以通過重寫訊息的處理函式,來窺探這些訊息內容,請看如下代碼:
internal class CRForm : Form
{
protected override void WndProc(ref Message m)
{
Console.WriteLine("CRForm WndProc: {0}", m.ToString());
base.WndProc(ref m);
}
}
訊息機制小結
通過以上代碼,你應該對訊息機制有了一個直觀的描述,那么,下面會說到我們的今天的主角——熱鍵,由于熱鍵被觸發的時候,也是通過訊息機制告知應用程式的,因此我們當然要會處理熱鍵訊息啦,相信你現在已經可以寫出對應的代碼了,
匯入相關 API
注冊全域熱鍵和撤銷全域熱鍵的 API 檔案如下,共你去查閱,
RegisterHotKey
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
UnregisterHotKey
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey
為了能把這兩個函式引入我們的程式,我們需要定義一個列舉類,如下:
/// <summary>
/// 為熱鍵提供修飾鍵選項的列舉,
/// </summary>
[Flags]
public enum KeyModifiers
{
/// <summary>
/// 沒有修飾鍵,
/// </summary>
None = 0X00,
/// <summary>
/// ALT 鍵,
/// </summary>
Alt = 0X01,
/// <summary>
/// CTRL 鍵,
/// </summary>
Control = 0X02,
/// <summary>
/// SHIFT 鍵,
/// </summary>
Shift = 0X04,
/// <summary>
/// Windows 徽標鍵,
/// </summary>
Windows = 0X08,
/// <summary>
/// 熱鍵按下時禁止重復發出訊息,
/// </summary>
NoRepeat = 0X4000
}
接著我們引入兩個API 函式和一個常量,如下:
/// <summary>
/// 匯入和定義 Windows SDK 中關于全域熱鍵函式及常量的靜態類,
/// </summary>
internal static class NativeMethods
{
/// <summary>
/// 定義使用 <see cref="RegisterHotKey(IntPtr, int, KeyModifiers, VirtualKeys)"/> 注冊的熱鍵觸發的訊息的訊息號,
/// </summary>
public const int WM_HOTKEY = 0X0312;
/// <summary>
/// 注冊系統全域熱鍵,
/// </summary>
/// <param name="hWnd">關聯的視窗句柄,如果此值為零,則與當前縣城關聯, WM_HOTKEY 訊息會放到當前縣城的訊息佇列,</param>
/// <param name="id">用來標識熱鍵的識別符號,</param>
/// <param name="fsModifiers">修飾鍵和選項的值,</param>
/// <param name="vk">虛擬鍵代碼,</param>
/// <returns>成功回傳 true, 失敗回傳 false,如需錯誤資訊可呼叫 <see cref="Marshal.GetLastWin32Error"/> 方法,</returns>
/// <seealso cref="UnregisterHotKey(IntPtr, int)"/>
/// <remarks>
/// 當鍵被按下時,系統會尋找匹配的已注冊的全域熱鍵,如果該全域熱鍵與一個表單關聯,則 <see cref="WM_HOTKEY"/> 訊息會放到該表單的訊息佇列,若未與一個表單關聯,則將 <see cref="WM_HOTKEY"/> 訊息發送到對應的執行緒訊息佇列,
/// 該函式無法將全域熱鍵與另一個執行緒創建的表單關聯,
/// 如果將要注冊的全域熱鍵已被注冊,呼叫該函式將失敗,
/// 如果已注冊的全域熱鍵具有與將要注冊的全域熱鍵相同的表單句柄 (hWnd) 和識別符號 (id), 則新注冊的全域熱鍵與舊全域熱鍵一起維護, 如果就全域熱鍵需要被新全域熱鍵替換,應該先顯示地呼叫 <see cref="UnregisterHotKey(IntPtr, int)"/> 函式以撤銷注冊的全域熱鍵, 接著呼叫該函式注冊新的全域熱鍵,
/// 在 Windows Server 2003 上: 新全域熱鍵與以注冊的全域熱鍵具有相同的表單句柄 (hWnd) 和識別符號 (id) 時, 舊全域熱鍵將被新的全域熱鍵替換,
/// F12 應當保留給除錯器使用,
/// 應用程式必須指定 0x0000 到 0xBFFF之間的值, 共享類別庫必須指定 0xC000 到 0xFFFF 之間的值給 id 引數,
/// </remarks>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);
/// <summary>
/// 撤銷已經注冊的系統全域熱鍵,
/// </summary>
/// <param name="hWnd">關聯的視窗句柄,如果沒有與任何視窗關聯,則必須為零,</param>
/// <param name="id">需要撤銷的熱鍵的識別符號,</param>
/// <returns>成功回傳 true, 失敗回傳 false,如需錯誤資訊可呼叫 <see cref="Marshal.GetLastWin32Error"/>方法,</returns>
/// <seealso cref="RegisterHotKey(IntPtr, int, KeyModifiers, VirtualKeys)"/>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
以上,我們就準備好了相關的型別和平臺呼叫的定義代碼,
使用熱鍵的流程
使用熱鍵的流程如下:
在必要的時候注冊需要的熱鍵,
在必要的時候釋放注冊的熱鍵,
處理好熱鍵訊息,
關聯到表單的熱鍵實體
注冊熱鍵
下面我們通過注冊一個 Ctrl + Shift + H 這一熱鍵,演示關聯到表單的熱鍵的作業流程,首先,區分不同熱鍵的方法是指定不同的 id 識別符號,我們首先定義一個常量,規定我們這個熱鍵的識別符號:
/// <summary>
/// 定義用于改變表單顯示狀態熱鍵的識別符號,
/// </summary>
const int ChangeVisibleHotKeyId = 1;
接著我們在表單的 Load 事件下撰寫如下代碼,注冊我們需要的熱鍵,
private void CRForm_Load(object sender, EventArgs e)
{
NativeMethods.RegisterHotKey(this.Handle, ChangeVisibleHotKeyId, KeyModifiers.Control | KeyModifiers.Shift, Keys.H);
}
處理熱鍵
為了使該熱鍵能實作對應的功能,我們應該重寫表單的處理函式,并且,把 WM_HOTKEY 訊息拿出來,并且派遣到另外一個方法實作具體的功能,代碼如下:
protected override void WndProc(ref Message m)
{
Console.WriteLine("CRForm WndProc: {0}", m.ToString());
// 根據訊息 id 處理訊息,
switch (m.Msg)
{
case NativeMethods.WM_HOTKEY:
// 我們把熱鍵的 id 取出來,呼叫處理熱鍵的方法,
this.ProcessHotKeyMessage(m.WParam.ToInt32());
break;
default:
base.WndProc(ref m);
break;
}
}
/// <summary>
/// 處理熱鍵訊息,我們在這里實作熱鍵對應的功能,
/// </summary>
/// <param name="hotKeyId">熱鍵的識別符號,</param>
private void ProcessHotKeyMessage(int hotKeyId)
{
// 根據不同的id 區分不同的熱鍵,
switch (hotKeyId)
{
case ChangeVisibleHotKeyId:
this.Visible = !this.Visible;
break;
}
}
撤銷熱鍵
最后,我們在表單銷毀時撤銷我們注冊的熱鍵,代碼如下:
private void CRForm_FormClosed(object sender, FormClosedEventArgs e)
{
NativeMethods.UnregisterHotKey(this.Handle, ChangeVisibleHotKeyId);
}
以上,就完成了我們的熱鍵注冊作業了,可以執行程式試一下是否能正常作業,
更進一步
本文只是展示了關聯到表單的熱鍵的處理流程,還有一種情況是這樣的,我們的程式并不需要表單,那么顯然就不需要創建出來一個表單,那么應該如何處理這個熱鍵呢?沒錯,你可以在 MessageFilter 中對熱鍵訊息進行處理,
完整代碼
以下是本程式的完整代碼:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace HotKeyApp
{
internal class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.AddMessageFilter(new MyMessageFilter());
Application.Run(new CRForm());
}
}
internal class CRForm : Form
{
/// <summary>
/// 定義用于改變表單顯示狀態熱鍵的識別符號,
/// </summary>
const int ChangeVisibleHotKeyId = 1;
public CRForm()
{
this.Load += CRForm_Load;
this.FormClosed += CRForm_FormClosed;
}
private void CRForm_FormClosed(object sender, FormClosedEventArgs e)
{
NativeMethods.UnregisterHotKey(this.Handle, ChangeVisibleHotKeyId);
}
private void CRForm_Load(object sender, EventArgs e)
{
NativeMethods.RegisterHotKey(this.Handle, ChangeVisibleHotKeyId, KeyModifiers.Control | KeyModifiers.Shift, Keys.H);
}
protected override void WndProc(ref Message m)
{
Console.WriteLine("CRForm WndProc: {0}", m.ToString());
// 根據訊息 id 處理訊息,
switch (m.Msg)
{
case NativeMethods.WM_HOTKEY:
// 我們把熱鍵的 id 取出來,呼叫處理熱鍵的方法,
this.ProcessHotKeyMessage(m.WParam.ToInt32());
break;
default:
base.WndProc(ref m);
break;
}
}
/// <summary>
/// 處理熱鍵訊息,我們在這里實作熱鍵對應的功能,
/// </summary>
/// <param name="hotKeyId">熱鍵的識別符號,</param>
private void ProcessHotKeyMessage(int hotKeyId)
{
// 根據不同的id 區分不同的熱鍵,
switch (hotKeyId)
{
case ChangeVisibleHotKeyId:
this.Visible = !this.Visible;
break;
}
}
}
internal class MyMessageFilter : IMessageFilter
{
public bool PreFilterMessage(ref Message m)
{
Console.WriteLine("MyMessageFilter: {0}", m.ToString());
return false;
}
}
/// <summary>
/// 為熱鍵提供修飾鍵選項的列舉,
/// </summary>
[Flags]
public enum KeyModifiers
{
/// <summary>
/// 沒有修飾鍵,
/// </summary>
None = 0X00,
/// <summary>
/// ALT 鍵,
/// </summary>
Alt = 0X01,
/// <summary>
/// CTRL 鍵,
/// </summary>
Control = 0X02,
/// <summary>
/// SHIFT 鍵,
/// </summary>
Shift = 0X04,
/// <summary>
/// Windows 徽標鍵,
/// </summary>
Windows = 0X08,
/// <summary>
/// 熱鍵按下時禁止重復發出訊息,
/// </summary>
NoRepeat = 0X4000
}
/// <summary>
/// 匯入和定義 Windows SDK 中關于全域熱鍵函式及常量的靜態類,
/// </summary>
internal static class NativeMethods
{
/// <summary>
/// 定義使用 <see cref="RegisterHotKey(IntPtr, int, KeyModifiers, VirtualKeys)"/> 注冊的熱鍵觸發的訊息的訊息號,
/// </summary>
public const int WM_HOTKEY = 0X0312;
/// <summary>
/// 注冊系統全域熱鍵,
/// </summary>
/// <param name="hWnd">關聯的視窗句柄,如果此值為零,則與當前縣城關聯, WM_HOTKEY 訊息會放到當前縣城的訊息佇列,</param>
/// <param name="id">用來標識熱鍵的識別符號,</param>
/// <param name="fsModifiers">修飾鍵和選項的值,</param>
/// <param name="vk">虛擬鍵代碼,</param>
/// <returns>成功回傳 true, 失敗回傳 false,如需錯誤資訊可呼叫 <see cref="Marshal.GetLastWin32Error"/> 方法,</returns>
/// <seealso cref="UnregisterHotKey(IntPtr, int)"/>
/// <remarks>
/// 當鍵被按下時,系統會尋找匹配的已注冊的全域熱鍵,如果該全域熱鍵與一個表單關聯,則 <see cref="WM_HOTKEY"/> 訊息會放到該表單的訊息佇列,若未與一個表單關聯,則將 <see cref="WM_HOTKEY"/> 訊息發送到對應的執行緒訊息佇列,
/// 該函式無法將全域熱鍵與另一個執行緒創建的表單關聯,
/// 如果將要注冊的全域熱鍵已被注冊,呼叫該函式將失敗,
/// 如果已注冊的全域熱鍵具有與將要注冊的全域熱鍵相同的表單句柄 (hWnd) 和識別符號 (id), 則新注冊的全域熱鍵與舊全域熱鍵一起維護, 如果就全域熱鍵需要被新全域熱鍵替換,應該先顯示地呼叫 <see cref="UnregisterHotKey(IntPtr, int)"/> 函式以撤銷注冊的全域熱鍵, 接著呼叫該函式注冊新的全域熱鍵,
/// 在 Windows Server 2003 上: 新全域熱鍵與以注冊的全域熱鍵具有相同的表單句柄 (hWnd) 和識別符號 (id) 時, 舊全域熱鍵將被新的全域熱鍵替換,
/// F12 應當保留給除錯器使用,
/// 應用程式必須指定 0x0000 到 0xBFFF之間的值, 共享類別庫必須指定 0xC000 到 0xFFFF 之間的值給 id 引數,
/// </remarks>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);
/// <summary>
/// 撤銷已經注冊的系統全域熱鍵,
/// </summary>
/// <param name="hWnd">關聯的視窗句柄,如果沒有與任何視窗關聯,則必須為零,</param>
/// <param name="id">需要撤銷的熱鍵的識別符號,</param>
/// <returns>成功回傳 true, 失敗回傳 false,如需錯誤資訊可呼叫 <see cref="Marshal.GetLastWin32Error"/>方法,</returns>
/// <seealso cref="RegisterHotKey(IntPtr, int, KeyModifiers, VirtualKeys)"/>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
}
最后
最后,希望本文對于你有些許幫助,
參考資料
視窗訊息 (入門與 Win32 和 c + + 一起) - Win32 apps | Microsoft Docs
https://docs.microsoft.com/zh-cn/windows/win32/learnwin32/window-messages
RegisterHotKey function (winuser.h) - Win32 apps | Microsoft Docs
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey
UnregisterHotKey function (winuser.h) - Win32 apps | Microsoft Docs
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-unregisterhotkey
WM_HOTKEY 訊息 (Winuser.h) - Win32 apps | Microsoft Docs
https://docs.microsoft.com/zh-cn/windows/win32/inputdev/wm-hotkey
Application 類 (System.Windows.Forms) | Microsoft Docs
https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.forms.application?view=netframework-4.8
[本文作者:張賜榮]
知乎: @張賜榮
賜榮博客: www.prc.cx
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/428416.html
標籤:.NET技术
