もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
以前の記事で、キーボードを押したことにするというのをやった。
そのときは、「keybd_event」というAPIを使ってC++アプリで試作していたのだが、今回はC#アプリでpinvokeを使って「SendInput」というAPIを使ってやってみる。
やりたいこと その2
今回、別のウインドウ上で、特定のキーの組み合わせを押したことにしたかった。 (例えば、別のウインドウ上で「ALT + F4」を押して、そのアプリを閉じる、とか)
こちらのページを参考に、下記のようにやってみる。
- まず、対象のウインドウを探す
- ここは、別の実験で、WinUI3アプリでウインドウを探すというのを以前やってたWinUI3アプリでウインドウを探すというのを以前やってたのでそれを流用する
- 探したウインドウを「SetForegroundWindow()」関数で前面に出す
- その状態で「SendInput()」関数でキーボードを押したことにする
実験コード
下記のようにして、SendInputでキー入力を送る。
PInvoke.SetForegroundWindow(wd.hWnd); // ALT+F4を送る ★★★★★ ココが肝!! ★★★★★ var inputs = new INPUT[4]; inputs[0].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[0].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU; inputs[1].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[1].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4; inputs[2].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[2].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4; inputs[2].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP; inputs[3].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[3].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU; inputs[3].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP; var inputsSpan = new Span<INPUT>(inputs); var ret = PInvoke.SendInput(inputsSpan, Marshal.SizeOf<INPUT>());
※SendInput()の第二引数には、INPUT
構造体の1つ分の大きさ(byte数)を入れる。(配列全体ではない!)
その値がおかしな値だと、SendInputは失敗して、戻り値が0になる。
成功すると、成功したキー操作の数が帰ってくる。(上の例だと4)
実験コード全体
下記に置いている。
using Microsoft.UI.Xaml; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; using Windows.Graphics; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.UI.Input.KeyboardAndMouse; namespace WindowMessageSender { public sealed partial class MainWindow : Window, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChanged(string propertyName) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); internal ObservableCollection<WindowData> WindowTitle { get { return _windowTitle; } set { _windowTitle = value; OnPropertyChanged(nameof(WindowTitle)); } } private ObservableCollection<WindowData> _windowTitle = new(); public MainWindow() { this.InitializeComponent(); // WinUI3のウインドウのハンドルを取得 var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this); // そのウインドウのIDを取得 Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd); // そこからAppWindowを取得する Microsoft.UI.Windowing.AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId); appWindow.Resize(new SizeInt32(700, 900)); } private void myButton_Click(object sender, RoutedEventArgs e) { WindowTitle.Clear(); string windowName; string windoClass; PInvoke.EnumWindows(((hWnd, param) => { int bufferSize = PInvoke.GetWindowTextLength(hWnd) + 1; unsafe { var isVisible = PInvoke.IsWindowVisible(hWnd); fixed (char* windowNameChars = new char[bufferSize]) { if (PInvoke.GetWindowText(hWnd, windowNameChars, bufferSize) == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) throw new Win32Exception(errorCode); return true; } windowName = new string(windowNameChars); } fixed (char* windoClassChars = new char[256]) { if (PInvoke.GetClassName(hWnd, windoClassChars, 256) == 0) { int errorCode = Marshal.GetLastWin32Error(); if (errorCode != 0) throw new Win32Exception(errorCode); return true; } windoClass = new string(windoClassChars); } if (VisibleOnly.IsOn) { WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible)); } else if (isVisible == true) { WindowTitle.Add(new WindowData(hWnd, windowName, windoClass, isVisible)); } return true; } }), (LPARAM)0); WindowList.ItemsSource = WindowTitle; } private void MenuFlyoutItem_Click(object sender, RoutedEventArgs e) { Button_Click(null, null); } private void Button_Click(object sender, RoutedEventArgs e) { if (WindowList.SelectedItem is WindowData wd) { var msg = UInt32.Parse(tbMsgNumber.Text, System.Globalization.NumberStyles.HexNumber); PInvoke.SetForegroundWindow(wd.hWnd); { // ALT+F4を送る ★★★★★ ココが肝!! ★★★★★ var inputs = new INPUT[4]; inputs[0].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[0].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU; inputs[1].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[1].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4; inputs[2].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[2].Anonymous.ki.wVk = VIRTUAL_KEY.VK_F4; inputs[2].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP; inputs[3].type = INPUT_TYPE.INPUT_KEYBOARD; inputs[3].Anonymous.ki.wVk = VIRTUAL_KEY.VK_MENU; inputs[3].Anonymous.ki.dwFlags = KEYBD_EVENT_FLAGS.KEYEVENTF_KEYUP; var inputsSpan = new Span<INPUT>(inputs); var ret = PInvoke.SendInput(inputsSpan, Marshal.SizeOf<INPUT>()); } } } } internal class WindowData { public HWND hWnd { get; set; } public string Title { get; set; } public string ClassName { get; set; } public bool IsVisible { get; set; } public WindowData(HWND hwnd, string title, string className, bool isVisible) { hWnd = hwnd; Title = title; ClassName = className; IsVisible = isVisible; } } }
参考
WinUI3でアプリでウインドウを探してそいつに好きなWM_Msgを送る実験
https://tera1707.com/entry/2024/07/16/214707
別ウインドウを操作する方法(別ウインドウにキーボード入力されたことにする方法)
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11186154546
SendInput関数
https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-sendinput
INPUT構造体
https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/ns-winuser-input
配列からSpanの作り方
https://qiita.com/aka-nse/items/cea3c6f91413c3582b5f
仮想キーコード(VK_XXX)一覧
https://learn.microsoft.com/ja-jp/windows/win32/inputdev/virtual-key-codes