アプリのウィンドウが非アクティブ状態でもキー入力を感知したくて以前試したのはこれ
gogowaten.hatenablog.com
でも、この方法ではタスクマネージャーなど、特定のアプリがアクティブウィンドウだと無反応だった
これを解決したのが今回
グローバルホットキーって言葉があって、WPF グローバルホットキーでググって
ここ
C#|グローバルホットキーを登録する – 貧脚レーサーのサボり日記
https://anis774.net/codevault/hotkey.html
と
WPFでホットキーの登録 - SourceChord
http://sourcechord.hatenablog.com/entry/2017/02/13/005456
キーをたくさん登録するときは、このあたりの方法が良さそう
数個でいいならここ
Nine Works WPFでHotKeyを設定する方法
http://nineworks2.blog.fc2.com/blog-entry-17.html
今回はここのをコピペ改変して
動作確認
登録したキーが押されたらこのウィンドウを表示する
ホットキーの登録
修飾キーはチェックボックスにチェックで、キーはコンボボックスから選択する
もしくは
コンボボックス上で登録したいキーを押す
で、
ボタンを押すと
必要ないけど確認ウィンドウ表示
Ctrl+テンキーの5を押すと
これが表示される
複数の修飾キーの組み合わせ
Alt + Ctrl + F12とかもできた
タスクマネージャーのウィンドウ上で実行
できた!
別のアプリで使われているホットキーは登録できない
すでに他のアプリで登録されているキーの組み合わせを登録しようとしてもできない
スクリーンショット系は登録できない?
SnapshotってのはPrintScreenキーのことで
Win + PrintScreen
Win + Alt + PrintScreen
この2つはウィンドウズで使われているみたいで登録できない
ってことは、アクティブウィンドウのスクリーンショットをクリップボードにコピーするAlt + PrintScreenもできなのかと思ったけど
これはできた
で、これを登録して押してみたら、ホットキーのほうが優先されてスクショはコピーされなかった
MainWindow.xaml
<Window x:Class="_20201210_グローバルホットキー3_2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:_20201210_グローバルホットキー3_2" mc:Ignorable="d" Title="MainWindow" Height="200" Width="260"> <Grid> <StackPanel> <GroupBox Header="ModKey修飾キー"> <StackPanel Orientation="Horizontal" Margin="2"> <CheckBox x:Name="MyChecAlt" Content="Alt" Margin="8"/> <CheckBox x:Name="MyChecCtrl" Content="Ctrl" Margin="8"/> <CheckBox x:Name="MyCheckShift" Content="Shift" Margin="8"/> <CheckBox x:Name="MyCheckWin" Content="Win" Margin="8"/> </StackPanel> </GroupBox> <TextBlock Text="+" HorizontalAlignment="Center"/> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <TextBlock Text="key = "/> <ComboBox Name="MyComboBoxKey" IsEditable="True" Width="100" PreviewKeyDown="MyComboBoxKey_PreviewKeyDown" PreviewKeyUp="MyComboBoxKey_PreviewKeyUp"/> </StackPanel> <Button x:Name="MyButton" Content="登録" Click="MyButton_Click" Margin="10"/> </StackPanel> </Grid> </Window>
コンボボックス上でキー入力したかったので、IsEditableをtrue指定
キーを押し下げたときと上げたときのイベントで、押されたキーの取得したかったので、PreviewKeyDownとPreviewKeyUpを使用
MainWindow.xaml.cs
using System; using System.Windows; using System.Windows.Input; using System.Windows.Interop; using System.Runtime.InteropServices; //Nine Works WPFでHotKeyを設定する方法 //http://nineworks2.blog.fc2.com/blog-entry-17.html //ここからのコピペ改変 //グローバルホットキーに任意のキーを登録 //修飾キーはチェックボックスで指定、普通のキーはコンボボックスから選択、もしくはコンボボックス上でキーを押す //登録は登録ボタン namespace _20201210_グローバルホットキー3_2 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { //登録用ID private const int HOTKEY_ID1 = 0x0001; private const int HOTKEY_ID2 = 0x0002; private const int WM_HOTKEY = 0x0312; private IntPtr MyWindowHandle; //必要なAPI参照 [DllImport("user32.dll")] private static extern int RegisterHotKey(IntPtr hWnd, int id, int modKey, int vKey); [DllImport("user32.dll")] private static extern int UnregisterHotKey(IntPtr hWnd, int id); public MainWindow() { InitializeComponent(); var host = new WindowInteropHelper(this); MyWindowHandle = host.Handle; //コンボボックス初期化 MyComboBoxKey.ItemsSource = Enum.GetValues(typeof(Key)); MyComboBoxKey.SelectedIndex = 0; ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcher_ThreadPreprocessMessage; this.Closed += MainWindow_Closed; } //アプリ終了時に登録解除 private void MainWindow_Closed(object sender, EventArgs e) { UnregisterHotKey(MyWindowHandle, HOTKEY_ID1); UnregisterHotKey(MyWindowHandle, HOTKEY_ID2); ComponentDispatcher.ThreadPreprocessMessage -= ComponentDispatcher_ThreadPreprocessMessage; } //ホットキー登録 private void MyButton_Click(object sender, RoutedEventArgs e) { int modifier = 0; string str = ""; if (MyChecAlt.IsChecked == true) { str += $" + Alt"; modifier = (int)ModifierKeys.Alt; } if (MyChecCtrl.IsChecked == true) { str += $" + Ctrl"; modifier += (int)ModifierKeys.Control; } if (MyCheckShift.IsChecked == true) { str += $" + Shift"; modifier += (int)ModifierKeys.Shift; } if (MyCheckWin.IsChecked == true) { str += $" + Win"; modifier += (int)ModifierKeys.Windows; } var key = (Key)MyComboBoxKey.SelectedValue; UnregisterHotKey(MyWindowHandle, HOTKEY_ID1); if (RegisterHotKey(MyWindowHandle, HOTKEY_ID1, modifier, KeyInterop.VirtualKeyFromKey(key)) == 0) { MessageBox.Show("登録に失敗"); } else { str += $" + {key}"; str = str.Remove(0, 3); MessageBox.Show($"{str} を登録しました"); } } //ホットキーが押されたときの動作 private void ComponentDispatcher_ThreadPreprocessMessage(ref MSG msg, ref bool handled) { if (msg.message != WM_HOTKEY) return; switch (msg.wParam.ToInt32()) { case HOTKEY_ID1: MessageBox.Show("HotKey1"); break; case HOTKEY_ID2: MessageBox.Show("HotKey2"); break; default: break; } } //コンボボックス上でキーを押し下げたとき //入力されたキー文字は無視 private void MyComboBoxKey_PreviewKeyDown(object sender, KeyEventArgs e) { e.Handled = true;//キーイベント無視む~し } //コンボボックス上でキーが上げられたとき //修飾キー以外なら、そのキーと同じキーをコンボボックスで選択する //文字は無視 private void MyComboBoxKey_PreviewKeyUp(object sender, KeyEventArgs e) { var key = e.Key; if ((key == Key.LeftAlt || key == Key.RightAlt || key == Key.LeftCtrl || key == Key.RightCtrl || key == Key.LeftShift || key == Key.RightShift || key == Key.LWin || key == Key.RWin) == false) { MyComboBoxKey.SelectedValue = key; } e.Handled = true; } } }
コンボボックスのitemSourceにKey列挙型をそのまま指定
ラクなんだけど
名前が微妙に違う、NextはPageDownキーのこと、SnapshotはPrintScreenキーだし、SelectやExecuteとかは何のキーかわからんw
他にも
ShiftキーやCtrlキーなんかの修飾キーは、表示しないほうがいいねえ
コンボボックス上でのキーイベント
PreviewKeyDownイベントだとPrintScreenキーだけ感知できなかったので、Upのほうで処理している
120行目の長いifは修飾キーを無視するためのもの
前回はタイマーを使って一定間隔でキーの状態を取得して判定していたけど、今回のRegisterHotKeyはタイマーが必要ないぶん簡単になった
アプリのウィンドウが非アクティブ状態でも
キーが押されたのを感知したいときはRegisterHotKeyを使う
キーの状態を取得するときはGetAsyncKeyStateやGetKeyStateを使う
ってかんじかなあ
あとGetAsyncKeyStateやGetKeyStateはめんどくさいけど、他のアプリで登録されているキーでも感知できるってのはいいねえ
関連記事
次回は18日後の2020/12/28、ようやくできた
gogowaten.hatenablog.com