【C#】サイズと表示位置を指定してスクリーンキーボードを起動する
概要
ひょんなことからスクリーンキーボード(osk.exe)を起動するプログラムを作ることになりました。タイトルにあるとおり、キーボードのサイズと表示位置を制御したいとのこと。またWindowsAPIかよ(^ω^)
環境
Windows10
Visual Studio Community 2017
スクリーンキーボードを起動
まずはキーボードを起動してみましょう。
static void Main(string[] args) { using (Process process = new Process()) { process.StartInfo.FileName = "osk.exe"; process.Start(); } Console.ReadKey(); }
アラートが。
スクリーン キーボードを起動できません。
stackoverflowによると、Any CPUビルドだとダメっぽい。プロジェクトプロパティのビルドの設定で、32ビットを優先のチェックボックスを外すか、x64ビルドとすれば動くとのことだったのでプラットフォームターゲットをx64でビルドすることで起動することを確認しました。
サイズと表示位置を指定する
SetWindowPos
というWindowsAPIを使えば実現できそうだったのですが、引数のウィンドウハンドルがprocess.MainWindowHandle
では取れない!取得するまでにやたら時間がかかるので、タイムアウトを設けておいて取得できるまでひたすら頑張る。遅ぇぇぇ
while (0 >= (int)process.MainWindowHandle) { if (IsTimeout(startTime, timeSpan)) { Console.WriteLine("Timeout"); Environment.Exit(0); } System.Threading.Thread.Sleep(1000); Console.WriteLine("..."); process.Refresh(); }
これでサイズと表示位置を制御できると思ったら早かった。うまく動かないんです。Marshal.GetLastWin32Error()
でエラーコードを取得してエラーメッセージを見てみたらアクセス拒否!管理者権限で実行しないとダメでした。くぅー、そんなん書いてある?見つけられなかったよ。。。
苦労したけど、なんとか動いた全部入りコードがこちら。
using System; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace OnScreenKeyboard { class Program { private class User32 { internal const UInt32 WM_SYSCOMMAND = 0x112; internal const UInt32 SC_RESTORE = 0xf120; [DllImport("user32.dll", CharSet = CharSet.Auto)] internal static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); internal const int SWP_NOSIZE = 0x0001; internal const int SWP_NOMOVE = 0x0002; internal const int SWP_NOZORDER = 0x0004; internal const int SWP_SHOWWINDOW = 0x0040; internal const int SWP_ASYNCWINDOWPOS = 0x4000; internal const int HWND_TOP = 0; internal const int HWND_BOTTOM = 1; internal const int HWND_TOPMOST = -1; internal const int HWND_NOTOPMOST = -2; } private class Kernel32 { [DllImport("kernel32.dll")] internal static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments); internal const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000; internal static string GetMessage(int errorCode) { var message = new StringBuilder(255); FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, (uint)errorCode, 0, message, message.Capacity, IntPtr.Zero ); return message.ToString(); } } static void Main(string[] args) { var onScreenKeyboad = "osk.exe"; IntPtr windowHandle = IntPtr.Zero; string processName = System.IO.Path.GetFileNameWithoutExtension(onScreenKeyboad); var query = from process in Process.GetProcesses() where process.ProcessName == processName select process; var keyboardProcess = query.FirstOrDefault(); if (keyboardProcess == null) { using (Process process = new Process()) { process.StartInfo.FileName = onScreenKeyboad; process.Start(); process.WaitForInputIdle(); var startTime = DateTime.Now; var timeSpan = new TimeSpan(0, 0, 10); while (0 >= (int)process.MainWindowHandle) { if (IsTimeout(startTime, timeSpan)) { Console.WriteLine("Timeout"); Environment.Exit(0); } System.Threading.Thread.Sleep(1000); Console.WriteLine("..."); process.Refresh(); } windowHandle = process.MainWindowHandle; var result = User32.SetWindowPos(windowHandle, 0, 0, 500, 1000, 300, User32.SWP_NOZORDER | User32.SWP_SHOWWINDOW); if (!result) { var errorCode = Marshal.GetLastWin32Error(); var message = Kernel32.GetMessage(errorCode); Console.WriteLine(message); } } } else { windowHandle = keyboardProcess.MainWindowHandle; User32.SendMessage(windowHandle, User32.WM_SYSCOMMAND, new IntPtr(User32.SC_RESTORE), new IntPtr(0)); Console.WriteLine("ReOpen"); } Console.WriteLine("Standby"); Console.ReadKey(); } static bool IsTimeout(DateTime startTime, TimeSpan timeSpan) { if (DateTime.Now - startTime > timeSpan) { return true; } return false; } } }
頭で考えるな、感じるんだ!
【C#】カーソル位置の取得
概要
カーソルの座標が知りたいんだけど、Win32APIで取れるんじゃね?って言われたので書いてみることに。System.Windows.Forms
のCursor
クラス使ったほうがいいともあったので両方試してみます。XとYの値表示するだけだからコンソールアプリでいいよね。
環境
Visual Studio Community 2017
System.Windows.Forms版の実装
あら、簡単。System.Windows.Forms
を参照に追加できない特別な理由がないならこっちでいいんじゃないかな。
using System; using System.Threading; using System.Windows.Forms; namespace CursorPosition { class Program { static void Main(string[] args) { while (true) { var pt = Cursor.Position; Console.WriteLine($"X:{pt.X} Y:{pt.Y}"); Thread.Sleep(100); Console.Clear(); } } } }
Win32API版の実装
今の現場の人たちすぐWin32API使ってなんとかしようとするんですよね。そんなん使ったことないよ!初めての実装なので雰囲気だけでも掴めたらokでしょう。
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Threading; namespace Win32APICursorPosition { class Program { #region Win32API [DllImport("User32.dll")] static extern bool GetCursorPos(out POINT lppoint); [StructLayout(LayoutKind.Sequential)] struct POINT { public int X { get; set; } public int Y { get; set; } public static implicit operator Point(POINT point) { return new Point(point.X, point.Y); } } #endregion public static Point GetCursorPosition() { var pt = new POINT(); GetCursorPos(out pt); return pt; } static void Main(string[] args) { while (true) { var pt = GetCursorPosition(); Console.WriteLine($"X:{pt.X} Y:{pt.Y}"); Thread.Sleep(100); Console.Clear(); } } } }