WPF快捷鍵實現主要有自定義快捷鍵命令和全局快捷鍵兩種方式。前端
自定義快捷鍵命令方式是經過KeyBinding爲命令綁定快捷鍵,按鍵組合可以使用「+」進行鏈接。能夠經過Modifiers+Key和Gesture兩種方式定義快捷鍵組合。能夠任選其一進行使用,MSDN中建議使用Gesture方式定義以避免發生混淆。ide
<Window.InputBindings> <KeyBinding Modifiers="Control+Alt" Key="Z" Command="{StaticResource CaptureScreen}" /> <KeyBinding Gesture="Control+Alt+Q" Command="{StaticResource FullScreen}" /> </Window.InputBindings>
全局快捷鍵方式是經過調用Windows API的RegisterHotKey函數來實現全局快捷鍵註冊,調用UnregisterHotKey函數實現全局快捷鍵註銷。這種方式WinForm和WPF通用。和自定義命令方式不一樣的是這種方式是在系統範圍內定義熱鍵,而前者是在窗口範圍內定義。窗口範圍內定義的快捷鍵觸發條件不只要求窗口可見,而且要求窗口獲取鍵盤焦點。這裏引入的問題是,若是命令的目標不具有獲取鍵盤焦點的能力,則命令將會無效。而且,和系統範圍內定義的快捷鍵相沖突時,優先級要低。函數
若是是Ribbon界面菜單,推薦使用自定義快捷鍵命令的方式。經過CanExecute方法控制當前命令在目標元素上是否可用,目標元素顯示可用或禁用狀態。若是是窗口無焦點下觸發快捷鍵,則只能選用全局快捷鍵方式了。this
如下是熱鍵設置的界面。接下來對全局快捷鍵的實現分步驟說明。spa
這是XAML頁面的代碼,這裏有界面元素的定義。線程
...... <ItemsControl Margin="10" ItemsSource="{Binding HotKeyList,ElementName=win}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="7"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <CheckBox Grid.Column="0" Content="{Binding Name}" IsChecked="{Binding IsUsable}" Style="{StaticResource ckbStyle1}" /> <CheckBox Grid.Column="1" Content="Ctrl" IsChecked="{Binding IsSelectCtrl}" IsEnabled="{Binding IsUsable}" Style="{StaticResource ckbStyle2}" /> <CheckBox Grid.Column="2" Content="Shift" IsChecked="{Binding IsSelectShift}" IsEnabled="{Binding IsUsable}" Style="{StaticResource ckbStyle2}" /> <CheckBox Grid.Column="3" Content="Alt" IsChecked="{Binding IsSelectAlt}" IsEnabled="{Binding IsUsable}" Style="{StaticResource ckbStyle2}" /> <ComboBox Grid.Column="4" ItemsSource="{Binding Keys}" SelectedItem="{Binding SelectKey}" IsEnabled="{Binding IsUsable}" Style="{StaticResource cmbStyle1}" /> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> ......
首先,新建一個自定義按鍵枚舉。WinForm中能夠使用Keys枚舉轉換,WPF中Key枚舉是不正確的,應該使用system.Windows.Froms.Keys枚舉,或者自定義正確的枚舉或int常量。由於這裏定義的枚舉會做爲快捷鍵設置的可選項,能夠只定義須要擊鍵。code
/// <summary>
/// 自定義按鍵枚舉 /// </summary> public enum EKey { Space = 32, Left = 37, Up = 38, Right = 39, Down = 40, A = 65, B = 66, C = 67, D = 68, ...... }
新建快捷鍵模型類。在模型中,能夠直接將EKey枚舉值集合綁定到界面的ComboBox上。orm
/// <summary> /// 快捷鍵模型 /// </summary> public class HotKeyModel { /// <summary> /// 設置項名稱 /// </summary> public string Name { get; set; } /// <summary> /// 設置項快捷鍵是否可用 /// </summary> public bool IsUsable { get; set; } /// <summary> /// 是否勾選Ctrl按鍵 /// </summary> public bool IsSelectCtrl { get; set; } /// <summary> /// 是否勾選Shift按鍵 /// </summary> public bool IsSelectShift { get; set; } /// <summary> /// 是否勾選Alt按鍵 /// </summary> public bool IsSelectAlt { get; set; } /// <summary> /// 選中的按鍵 /// </summary> public EKey SelectKey { get; set; } /// <summary> /// 快捷鍵按鍵集合 /// </summary> public static Array Keys { get { return Enum.GetValues(typeof(EKey)); } } }
WM_HOTKEY爲熱鍵消息,在用戶鍵入被RegisterHotKey函數註冊的熱鍵時發送。該消息將位於隊列的最前端,而且與註冊了這個熱鍵的進程關聯。blog
RegisterHotKey函數定義一個系統範圍內的熱鍵。 參數hWnd是接收熱鍵產生WM_HOTKEY消息的窗口句柄。若該參數null,傳遞給調用線程的WM_HOTKEY消息必須在消息循環中進行處理。 參數id爲定義熱鍵的標識符。調用線程中的其餘熱鍵,不能使用一樣的標識符。爲了不與其餘動態連接庫定義的熱鍵衝突,一個DLL必須使用GlobalAddAtom函數獲取熱鍵的標識符。 參數fsModifiers是定義爲了產生WM_HOTKEY消息而必須與由nVirKey參數定義的鍵一塊兒按下的鍵,即Ctrl、Shift和Alt的按鍵組合。 參數vk是定義熱鍵的虛擬鍵碼,也就是選中的EKey中的按鍵。 PS:當某鍵被接下時,系統在全部的熱鍵中尋找匹配者。一旦找到一個匹配的熱鍵,系統將把WM_HOTKEY消息傳遞給登記了該熱鍵的線程的消息隊列。該消息被傳送到隊列頭部,所以它將在下一輪消息循環中被移除。該函數不能將熱鍵同其餘線程建立的窗口關聯起來。若爲一熱鍵定義的擊鍵已被其餘熱鍵所定義,則RegisterHotKey函數調用失敗。若hWnd參數標識的窗口已用與id參數定義的相同的標識符登記了一個熱鍵,則參數fsModifiers和vk的新值將替代這些參數先前定義的值。隊列
UnregisterHotKey函數釋放調用線程先前登記的熱鍵。 參數hWnd與被釋放的熱鍵相關的窗口句柄。若熱鍵不與窗口相關,則該參數爲null。
GlobalAddAtom函數是向全局原子表添加一個字符串,並返回這個字符串的惟一標識符(原子ATOM)。Win32系統中,爲了實現信息共享,系統維護了一張全局原子表,用於保存字符串與之對應的標識符的組合。 參數lpString爲一個字符串,這個字符串的長度最大爲255字節。返回值爲一個short類型的原子。若函數調用失敗,則返回值爲0。 PS:若是字符串中已經存在於全局原子表中,則返回現有的字符串的原子,而且原子的引用計數加1。與原子相關的字符串不會從內存中刪除,直到它的引用計數爲零。全局原子不會在應用程序終止時自動刪除。每次調用GlobalAddAtom函數,必須相應的調用GlobalDeleteAtom函數刪除原子。
GlobalFindAtom函數是在表中搜索全局原子。 參數lpString爲一個字符串。找到返回原子,沒找到返回0。
GlobalDeleteAtom函數是在表中刪除全局原子。 參數nAtom爲全局原子。
/// <summary> /// 熱鍵管理器 /// </summary> public class HotKeyManager { /// <summary> /// 熱鍵消息 /// </summary> public const int WM_HOTKEY = 0x312; /// <summary> /// 註冊熱鍵 /// </summary> [DllImport("user32.dll", SetLastError = true)] public static extern bool RegisterHotKey(IntPtr hWnd, int id, ModifierKeys fsModifuers, int vk); /// <summary> /// 註銷熱鍵 /// </summary> [DllImport("user32.dll", SetLastError = true)] public static extern bool UnregisterHotKey(IntPtr hWnd, int id); /// <summary> /// 向原子表中添加全局原子 /// </summary> [DllImport("kernel32.dll", SetLastError = true)] public static extern short GlobalAddAtom(string lpString); /// <summary> /// 在表中搜索全局原子 /// </summary> [DllImport("kernel32.dll", SetLastError = true)] public static extern short GlobalFindAtom(string lpString); /// <summary> /// 在表中刪除全局原子 /// </summary> [DllImport("kernel32.dll", SetLastError = true)] public static extern short GlobalDeleteAtom(string nAtom); }
首先,重載OnSourceInitialized函數,這個事件發生在WPF窗體的資源初始化完成,而且能夠經過WindowInteropHelper得到該窗體的句柄用來與Win32交互後。調用HwndSource.FromHwnd方法獲取當前窗口句柄,再調用hWndSource.AddHook方法添加接收全部窗口消息的事件處理程序。重載OnContentRendered函數,在控件初始化完成後初始化快捷鍵。
protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); // 獲取窗體句柄 m_Hwnd = new WindowInteropHelper(this).Handle; HwndSource hWndSource = HwndSource.FromHwnd(m_Hwnd); // 添加處理程序 if (hWndSource != null) hWndSource.AddHook(WndProc); } /// <summary> /// 全部控件初始化完成後調用 /// </summary> /// <param name="e"></param> protected override void OnContentRendered(EventArgs e) { base.OnContentRendered(e); // 註冊熱鍵 InitHotKey(); }
註冊全局快捷鍵到系統中。
/// <summary> /// 初始化註冊快捷鍵 /// </summary> /// <param name="hotKeyModelList">待註冊熱鍵的項</param> /// <returns>true:保存快捷鍵的值;false:彈出設置窗體</returns> private bool InitHotKey(ObservableCollection<SysParameterSettingHotKeyModel> hotKeyModelList = null) { var list = hotKeyModelList ?? SysParameterSettingsManager.Instance.LoadDefaultHotKey(); // 註冊全局快捷鍵 string failList = HotKeyHelper.RegisterGlobalHotKey(list, m_Hwnd, out m_HotKeySettings); if (string.IsNullOrEmpty(failList)) return true; MessageBoxResult mbResult = MessageBoxWindow.Show("提示", string.Format("沒法註冊下列快捷鍵\n\r{0}是否要改變這些快捷鍵?", failList), MessageBoxButton.YesNo); var win = SysParameterSettingsWindow.CreateInstance(); if (mbResult == MessageBoxResult.Yes) { win.hotKeySet.IsSelected = true; if (!win.IsVisible) { win.ShowDialog(); } else { win.Activate(); } return false; } return true; }
新建一個熱鍵註冊幫助類HotKeyHelper。這裏有兩個須要注意的地方。第一個是全局原子不會在應用程序終止時自動刪除,每次調用GlobalAddAtom函數,必須相應的調用GlobalDeleteAtom函數刪除原子。第二是若爲一熱鍵定義的擊鍵已被其餘熱鍵所定義,則RegisterHotKey函數調用失敗,因此每次調用RegisterHotKey函數註冊熱鍵時,必須先調用UnregisterHotKey函數註銷舊的熱鍵。
/// <summary> /// 熱鍵註冊幫助 /// </summary> public class HotKeyHelper { /// <summary> /// 記錄快捷鍵註冊項的惟一標識符 /// </summary> private static Dictionary<EHotKeySetting, int> m_HotKeySettingsDic = new Dictionary<EHotKeySetting, int>(); /// <summary> /// 註冊系統快捷鍵 /// </summary> /// <param name="hotKeyModelList">待註冊快捷鍵項</param> /// <param name="hwnd">窗口句柄</param> /// <param name="hotKeySettingsDic">快捷鍵註冊項的惟一標識符字典</param> /// <returns>返回註冊失敗項的拼接字符串</returns> public static string RegisterSystemHotKey(IEnumerable<HotKeyModel> hotKeyModelList, IntPtr hwnd, out Dictionary<EHotKeySetting, int> hotKeySettingsDic) { string failList = string.Empty; foreach (var item in hotKeyModelList) { if (!RegisterHotKey(item, hwnd)) { string str = string.Empty; if (item.IsSelectCtrl && !item.IsSelectShift && !item.IsSelectAlt) { str = ModifierKeys.Control.ToString(); } else if (!item.IsSelectCtrl && item.IsSelectShift && !item.IsSelectAlt) { str = ModifierKeys.Shift.ToString(); } else if (!item.IsSelectCtrl && !item.IsSelectShift && item.IsSelectAlt) { str = ModifierKeys.Alt.ToString(); } else if (item.IsSelectCtrl && item.IsSelectShift && !item.IsSelectAlt) { str = string.Format("{0}+{1}", ModifierKeys.Control.ToString(), ModifierKeys.Shift); } else if (item.IsSelectCtrl && !item.IsSelectShift && item.IsSelectAlt) { str = string.Format("{0}+{1}", ModifierKeys.Control.ToString(), ModifierKeys.Alt); } else if (!item.IsSelectCtrl && item.IsSelectShift && item.IsSelectAlt) { str = string.Format("{0}+{1}", ModifierKeys.Shift.ToString(), ModifierKeys.Alt); } else if (item.IsSelectCtrl && item.IsSelectShift && item.IsSelectAlt) { str = string.Format("{0}+{1}+{2}", ModifierKeys.Control.ToString(), ModifierKeys.Shift.ToString(), ModifierKeys.Alt); } str += string.Format("+{0}", item.SelectKey.ToString()); str = string.Format("{0} ({1})\n\r", item.Name, str); failList += str; } } hotKeySettingsDic = m_HotKeySettingsDic; return failList; } /// <summary> /// 註冊熱鍵 /// </summary> /// <param name="hotKeyModel">熱鍵待註冊項</param> /// <param name="hWnd">窗口句柄</param> /// <returns>成功返回true,失敗返回false</returns> private static bool RegisterHotKey(HotKeyModel hotKeyModel, IntPtr hWnd) { var fsModifierKey = new ModifierKeys(); var hotKeySetting = (EHotKeySetting)Enum.Parse(typeof(EHotKeySetting), hotKeyModel.Name); if (!m_HotKeySettingsDic.ContainsKey(hotKeySetting)) { // 全局原子不會在應用程序終止時自動刪除。每次調用GlobalAddAtom函數,必須相應的調用GlobalDeleteAtom函數刪除原子。 if (HotKeyManager.GlobalFindAtom(hotKeySetting.ToString()) != 0) { HotKeyManager.GlobalDeleteAtom(HotKeyManager.GlobalFindAtom(hotKeySetting.ToString())); } // 獲取惟一標識符 m_HotKeySettingsDic[hotKeySetting] = HotKeyManager.GlobalAddAtom(hotKeySetting.ToString()); } else { // 註銷舊的熱鍵 HotKeyManager.UnregisterHotKey(hWnd, m_HotKeySettingsDic[hotKeySetting]); } if (!hotKeyModel.IsUsable) return true; // 註冊熱鍵 if (hotKeyModel.IsSelectCtrl && !hotKeyModel.IsSelectShift && !hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Control; } else if (!hotKeyModel.IsSelectCtrl && hotKeyModel.IsSelectShift && !hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Shift; } else if (!hotKeyModel.IsSelectCtrl && !hotKeyModel.IsSelectShift && hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Alt; } else if (hotKeyModel.IsSelectCtrl && hotKeyModel.IsSelectShift && !hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Control | ModifierKeys.Shift; } else if (hotKeyModel.IsSelectCtrl && !hotKeyModel.IsSelectShift && hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Control | ModifierKeys.Alt; } else if (!hotKeyModel.IsSelectCtrl && hotKeyModel.IsSelectShift && hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Shift | ModifierKeys.Alt; } else if (hotKeyModel.IsSelectCtrl && hotKeyModel.IsSelectShift && hotKeyModel.IsSelectAlt) { fsModifierKey = ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt; } return HotKeyManager.RegisterHotKey(hWnd, m_HotKeySettingsDic[hotKeySetting], fsModifierKey, (int)hotKeyModel.SelectKey); } }
經過判斷msg 是否爲WM_HOTKEY來判斷當前快捷鍵是否觸發。經過附加參數wideParam得到當前快捷鍵觸發的項,進而進行相應處理。另外,當前消息處理完成後須要將handled置爲true。
/// <summary> /// 窗體回調函數,接收全部窗體消息的事件處理函數 /// </summary> /// <param name="hWnd">窗口句柄</param> /// <param name="msg">消息</param> /// <param name="wideParam">附加參數1</param> /// <param name="longParam">附加參數2</param> /// <param name="handled">是否處理</param> /// <returns>返回句柄</returns> private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wideParam, IntPtr longParam, ref bool handled) { var hotkeySetting = new EHotKeySetting(); switch (msg) { case HotKeyManager.WM_HOTKEY: int sid = wideParam.ToInt32(); if (sid == m_HotKeySettings[EHotKeySetting.全屏]) { hotkeySetting = EHotKeySetting.全屏; //TODO 執行全屏操做 } else if (sid == m_HotKeySettings[EHotKeySetting.截圖]) { hotkeySetting = EHotKeySetting.截圖; //TODO 執行截圖操做 } else if (sid == m_HotKeySettings[EHotKeySetting.播放]) { hotkeySetting = EHotKeySetting.播放; //TODO ...... } else if (sid == m_HotKeySettings[EHotKeySetting.前進]) { hotkeySetting = EHotKeySetting.前進; } else if (sid == m_HotKeySettings[EHotKeySetting.後退]) { hotkeySetting = EHotKeySetting.後退; } else if (sid == m_HotKeySettings[EHotKeySetting.保存]) { hotkeySetting = EHotKeySetting.保存; } else if (sid == m_HotKeySettings[EHotKeySetting.打開]) { hotkeySetting = EHotKeySetting.打開; } else if (sid == m_HotKeySettings[EHotKeySetting.新建]) { hotkeySetting = EHotKeySetting.新建; } else if (sid == m_HotKeySettings[EHotKeySetting.刪除]) { hotkeySetting = EHotKeySetting.刪除; }
MessageBox.Show(string.Format("觸發【{0}】快捷鍵", hotkeySetting.ToString())); handled = true; break; } return IntPtr.Zero; }