非アクティブ時にもキーの状態を取得してみたWindowsAPIのGetAsyncKeyState
Timerで一定時間間隔ごとに
GetAsyncKeyStateを実行してキーの状態を取得
2020WPF/20201110_WinApiでキーの状態取得 at master · gogowaten/2020WPF
github.com
環境
Visual Studio Community 2019
.NET Core 3.1、C#、WPF
<Window x:Class="_20201110_WinApiでキーの状態取得.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:_20201110_WinApiでキーの状態取得" mc:Ignorable="d" Title="MainWindow" Height="200" Width="400"> <Grid> <Grid.Resources> <Style TargetType="TextBlock"> <Setter Property="Margin" Value="0,2,0,2"/> </Style> <Style TargetType="StackPanel"> <Setter Property="Margin" Value="8,2,0,2"/> <Setter Property="Width" Value="120"/> </Style> </Grid.Resources> <StackPanel Orientation="Horizontal" Width="auto"> <StackPanel Orientation="Vertical"> <TextBlock Text="Key1"/> <ComboBox x:Name="MyCombo1Key"/> <TextBlock x:Name="MyTextBlockKey1AsyncState" Text="asyncState1"/> <TextBlock x:Name="MyTextBlockKey1State" Text="State1"/> </StackPanel> <StackPanel Orientation="Vertical"> <TextBlock Text="Key2"/> <ComboBox x:Name="MyCombo2Key"/> <TextBlock x:Name="MyTextBlockKey2AsyncState" Text="asyncState2"/> <TextBlock x:Name="MyTextBlockKey2State" Text="State2"/> </StackPanel> <StackPanel Width="100"> <TextBlock Text="Key1 を押しながら Key2 を押した回数" TextWrapping="Wrap"/> <TextBlock x:Name="MyTextBlockCount" Text="count" FontSize="20" HorizontalAlignment="Right"/> <Button x:Name="MyButtonCountReset" Content="リセット" Click="MyButtonCountReset_Click"/> </StackPanel> </StackPanel> </Grid> </Window>
MainWindow.xaml.cs
using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Input; using System.Windows.Threading; namespace _20201110_WinApiでキーの状態取得 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { //必要なAPIはGetAsyncKeyStateだけなんだけど //似たようなGetKeyStateも使ってみた [DllImport("user32.dll")] private static extern short GetAsyncKeyState(int vKey); [DllImport("user32.dll")] private static extern short GetKeyState(int vKey); //状態を知りたいキーの仮想キーコードを渡す //戻り値の型はshortで、これを2進数にして最上位ビットと最下位ビットで判定する //GetAsyncKeyState //最上位ビットが1のとき、押されている状態を表す //最下位ビットが1のとき、前回取得時から今回までに押された形跡があることを表す // 2進数 10進数(最上位、最下位以外が0のとき) // 0xxx_xxxx_xxxx_xxx0 0 押されてない、押された形跡無し // 0xxx_xxxx_xxxx_xxx1 1 押されてない、押された形跡あり // 1xxx_xxxx_xxxx_xxx0 -32768 押されている、押された形跡無し // 1xxx_xxxx_xxxx_xxx1 -32767 押されている、押された形跡あり //↑のxのところは0か1か決まっていないようだけど、見ていた感じでは常に0だった //ビットの判定はビット演算のandを使う //最下位ビットの判定、(戻り値 & 1) これが1なら1 //最上位ビットの判定、(戻り値 & 0x8000)してこれを右に15シフトして1なら1 //あとは、short型で最上位ビットが1ならマイナスの値になるはず?なので、戻り値 < 0 なら1 //GetKeyState //最上位ビットが1のとき、押されている状態 //最下位ビットはトグル式のキー(CapsLockとか)用で1のとき、トグルオン状態 //違い //GetAsyncKeyStateは押された形跡がわかる //GetKeyStateはトグル式のキーのトグル状態がわかる //イマイチ //GetAsyncKeyStateはアプリによっては取得できない(無反応?) //タスクマネージャー、エクセル、デスクトップ検索のEverythingなど private DispatcherTimer MyTimer; private int MyCount; public MainWindow() { InitializeComponent(); //タイマー初期化 MyTimer = new DispatcherTimer(); MyTimer.Tick += MyTimer_Tick; //時間間隔、8ミリ秒にしてみた MyTimer.Interval = new TimeSpan(0, 0, 0, 0, 8); MyTimer.Start(); //キー選択用のコンボボックスの初期化 MyCombo1Key.Items.Add(Key.RightCtrl); MyCombo1Key.Items.Add(Key.RightShift); MyCombo1Key.Items.Add(Key.RightAlt); MyCombo1Key.Items.Add(Key.Right); MyCombo2Key.Items.Add(Key.RightCtrl); MyCombo2Key.Items.Add(Key.RightShift); MyCombo2Key.Items.Add(Key.Right); MyCombo2Key.Items.Add(Key.A); MyCombo1Key.SelectedIndex = 1; MyCombo2Key.SelectedIndex = 3; } //一定時間間隔でキーの状態を取得して表示 private void MyTimer_Tick(object sender, EventArgs e) { //KeyをWindowsAPIで使う仮想キーコードに変換 var vKey1 = KeyInterop.VirtualKeyFromKey((Key)MyCombo1Key.SelectedItem); var vKey2 = KeyInterop.VirtualKeyFromKey((Key)MyCombo2Key.SelectedItem); //GetAsyncKeyStateでキーの状態を取得して値を表示 short key1AsyncState = GetAsyncKeyState(vKey1); short key2AsyncState = GetAsyncKeyState(vKey2); MyTextBlockKey1AsyncState.Text = "AsyncKey= " + key1AsyncState.ToString(); MyTextBlockKey2AsyncState.Text = "AsyncKey= " + key2AsyncState.ToString(); //GetKeyStateでキーの状態を取得して値を表示 short key1State = GetKeyState(vKey1); short key2State = GetKeyState(vKey2); MyTextBlockKey1State.Text = "Key= " + key1State.ToString(); MyTextBlockKey2State.Text = "Key= " + key2State.ToString(); //Key1が押された状態で、Key2も押されていたらの判定 // != 0 この判定の仕方は雑だけど問題なさそう if (key1AsyncState != 0 & (key2AsyncState & 1) == 1) { //カウントを増やして回数の表示を更新 MyCount++; MyTextBlockCount.Text = MyCount.ToString() + "回"; } //↑の雑じゃない判定版 //if (((key1AsyncState & 0x8000) >> 15 == 1) & ((key2AsyncState & 1) == 1)) { } //GetKeyStateとGetAsyncKeyState版でもできた //if ((key1State & 0x8000) >> 15 == 1 & (key2AsyncState & 1)== 1) //{ // MyCount++; // MyTextBlockCount.Text = MyCount.ToString() + "回"; //} //GetkeyStateだけだとできなかった //両キーとも押しっぱなしのときしか判定されない //if ((key1State & 0x8000) >> 15 == 1 & (key2State & 0x80) >> 7 == 1) //{ // MyCount++; // MyTextBlockCount.Text = MyCount.ToString() + "回"; //} //GetAsynckeyStateだけ → できた //GetkeyStateとGetAsynckeyState → できた //GetkeyStateだけ → むり? } //カウントリセット private void MyButtonCountReset_Click(object sender, RoutedEventArgs e) { MyCount = 0; MyTextBlockCount.Text = MyCount.ToString(); } } }
GetAsyncKeyStateとGetKeyStateの戻り値
戻り値の型はどちらもshort型のはず
押された状態のキーの戻り値は
GetAsyncKeyState:-32768か、-32767
GetKeyState:-128か、-127
押されていない状態では
GetAsyncKeyState:0
GetKeyState:1か、0
になった
これを2進数にして最上位ビットが1なら、押された状態
2進数にして最上位ビットをみると
-32768と-32767をはshort型のWORDで確かに1になっている
-128と-127は
途中の関係ない桁が1になっているけど、最上位ビットも1になっている、へー
参照したところ
Keyboard and Mouse Input - Win32 apps | Microsoft Docs
docs.microsoft.com
Keyを仮想キーコードに変換
System.Windows.Input.KeyInteropクラスの、VirtualKeyFromKeyを使う
Aキーの場合は
VirtualKeyFromKey(Key.A);
大体はこれで事足りるけど、左右に2つあるctrlキーを一度に取得したいときは
KT Software - 仮想キーコード一覧
kts.sakaiweb.com
ここを見ると載っている
変数の型でWORDとかDWORDってなんなん?ってときは
データ型 - Windows API 入門
kaitei.net
だいたいできたけど動かない場合もあって
タスクマネージャーとデスクトップ検索のEverythingでは、キーによってGetAsyncKeyStateやGetKeyStateが無反応?でキーを押しても常に0を返してくる
タスクマネージャー上では
CtrlキーはGetKeyStateだけ返ってくる
カーソルキーの右キーはどちらも無反応
フックするとかいうのを使うとできそうなんだけど、かなり難しそうだった
関連記事
1ヶ月後
gogowaten.hatenablog.com
こっちのほうがラクかな