1.21 jigowatts

Great Scott!

【C#】サイズと表示位置を指定してスクリーンキーボードを起動する

概要

ひょんなことからスクリーンキーボード(osk.exe)を起動するプログラムを作ることになりました。タイトルにあるとおり、キーボードのサイズと表示位置を制御したいとのこと。またWindowsAPIかよ(^ω^)

f:id:sh_yoshida:20180201011927p:plain

環境

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.FormsCursorクラス使ったほうがいいともあったので両方試してみます。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();
            }
        }
    }
}

2018

New Year

f:id:sh_yoshida:20180104233021j:plain

1月の転職活動から始まり、4月からは新しい環境で様々な経験を積むことが出来た一年になりました。あっという間に過ぎ去ったという感じ。日々の仕事が忙しすぎてモチベーションだだ下がりですが必死に抗います。

今年は得た知識は熱いうちに記事に還元するぞ٩( 'ω' )و

本年もよろしくお願いいたします!