C#でマウスクリッカーをつくる

ネットの掲示板にあがっていた質問。   やりたいこととしてあがっていた仕様はこちら:

  • 連打回数指定
  • 連打間隔指定
  • 連打秒数指定
  • スタート&ストップのホットキーを設定

いきなりですが、連打回数x連打間隔 = 連打秒数だと予想されますので連打秒数は今回作成外として放置させて頂きます、今回はホットキーを使うということなのでWindowsFormsのプロジェクトで作成します。まず手間のかかるホットキー、こちらのサイトを参考にさせていただき以下のようなクラスを作りました。※クラス名は似ていますが、機能は異なりますのでご注意ください。

ModiferKey.cs

namespace ClickerByCSharp
{
    public enum ModiferKey : uint
    {
        None = 0,
        Alt = 1,
        Control = 2,
        Shift = 4,
    }
}

HotKey.cs

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ClickerByCSharp
{
    public class Hotkey
    {
        [DllImport("user32.dll")]
        public static extern int RegisterHotKey(IntPtr hWnd, int id, ModiferKey modifer, Keys vk);
        [DllImport("user32.dll")]
        public static extern int UnregisterHotKey(IntPtr hWnd, int id);

        private ModiferKey _modifer;
        private Keys _hotKey;
        private int _id;
        private Form _hostForm;
        public Hotkey(ModiferKey modifer, Keys hotkey)
        {
            _modifer = modifer;
            _hotKey = hotkey;
        }

        public void Register(Form hostForm)
        {
            for (int i = 0; i < 0xbfff; i++)
            {
                if (RegisterHotKey(hostForm.Handle, i, _modifer, _hotKey) != 0)
                {
                    _hostForm = hostForm;
                    _id = i;
                }
            }
        }

        public void Unregister(){
            UnregisterHotKey(_hostForm.Handle, _id);
        }
    }
}

ホットキーはWinAPIのRegisterHotKey, UnregisterHotKeyで登録・解除をします。登録したホットキーが押されると、WM_HOTKEYメッセージが登録したウィンドウに対して飛んできます。このときウィンドウは最小化していてもちゃんとメッセージが飛んでくるので、ウィンドウは最小化されてもタスクトレイに表示されていても問題はありません。ここでは2重登録や解除漏れなどのエラー処理はしていませんが、登録解除漏れはIDisposableインターフェースを実装するなどして対応しておいたほうがよいと感じます。

これを設定するための簡単なUIをユーザーコントロールとして作成します。

hotkeyselector

HotKeySelectControl.cs

using System.Windows.Forms;

namespace ClickerByCSharp
{
    public partial class HotkeySelectControl : UserControl
    {
        private Keys hotkey = Keys.L;

        public HotkeySelectControl()
        {
            InitializeComponent();
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
        {
            if (Keys.A <= keyData && keyData <= Keys.Z){
                hotkey = keyData;
            }
            textBoxHotkey.Text = hotkey.ToString();
            return base.ProcessCmdKey(ref msg, keyData);
        }

        public Hotkey GetHotKey()
        {
            ModiferKey modifer = ModiferKey.None;
            modifer = checkBoxCtrl.Checked ? (modifer | ModiferKey.Control) : modifer;
            modifer = checkBoxAlt.Checked ? (modifer | ModiferKey.Alt) : modifer;
            modifer = checkBoxShift.Checked ? (modifer | ModiferKey.Shift) : modifer;
            return new Hotkey(modifer, hotkey);
        }
    }
}

ユーザ設定を元に、先ほど作ったHotKeyクラスインスタンスを取得するためだけです。1文字しか設定できないので、TextBoxのMaxLengthは1にしています。このUserControlをはりつけ、ホットキーのメッセージを受け取るための親Formを作成します。親FormでWM_HOTKEYを待つのですが、その時にHotkeyのIDが必要になるので、登録・解除処理は親Formから呼び出すことにします。このFormが起動直後に実行されるMainFormの想定です、前述の通りいきなりタスクトレイに引っ込んでもいいのですがなるべく本質的なところわかりやすくするため、通常表示のままにしておきます。

mouseclickform

MouseClickForm.cs

using System;
using System.Windows.Forms;

namespace ClickerByCSharp
{
    public partial class MouseClickForm : Form
    {
        private int elapsedTime;
        private Hotkey _hotKey;

        public MouseClickForm()
        {
            InitializeComponent();
        }

        private void buttonStartWithDelay_Click(object sender, EventArgs e)
        {
            elapsedTime = 0;
            timerCountdown.Enabled = true;
        }

        private void timerCountdown_Tick(object sender, EventArgs e)
        {
            const int timeToStart = 5;
            elapsedTime++;
            labelCountDown.Text = String.Format("{0}s後に開始...", timeToStart - elapsedTime);
            if (elapsedTime == timeToStart){
                timerCountdown.Enabled = false;
                ExecuteMouseClick();
            }
        }

        private void buttonRegister_Click(object sender, EventArgs e)
        {
            _hotKey = hotkeySelecter.GetHotKey();
            _hotKey.Register(this);
        }

        private void buttonUnregister_Click(object sender, EventArgs e)
        {
            _hotKey.Unregister();
            _hotKey = null;
        }

        protected override void WndProc(ref Message m)
        {
            const int WM_HOTKEY = 0x0312;
            base.WndProc(ref m);
            if ((m.Msg == WM_HOTKEY) && (_hotKey.ID == (int)m.WParam))
            {
                ExecuteMouseClick();
            }
        }

        private void ExecuteMouseClick()
        {
            var clicker = new SequentialMouseClick(
                (int)numericUpDownSequantialHitCount.Value,
                (int)numericUpDownHitSpan.Value);
            clicker.Start();
        }

    }
}

ホットキーの登録がうまくいかなかった場合に備えて、テストボタンを作っています。テストボタンでは5sからカウントダウンし、0sになると対象のマウスクリック処理を発動させます。ここまででホットキーの登録とホットキーが押された場合の処理ができました。上のクラスで天下り的に出てきた SequentialMouseClickクラス、これが最後につくるクラスです。

 SequantialMouseClick.cs

using System.Threading;
using System.Runtime.InteropServices;

namespace ClickerByCSharp
{
    public class SequentialMouseClick
    {
        private const int MOUSEEVENTF_LEFTDOWN = 0x2;
        private const int MOUSEEVENTF_LEFTUP = 0x4;
        [DllImport("user32.dll")]
        public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);

        private int _count;
        private int _intervalInSecond;
        private Thread _clickThread;

        public SequentialMouseClick(int count, int intervalInSecond){
            _clickThread = new Thread(new ThreadStart(ClickThread));
            _count = count;
            _intervalInSecond = intervalInSecond;
        }

        public void Start(){
            _clickThread.Start();
        }

        private void ClickThread(){
            int count = 0;
            while (count < _count)
            {
                Thread.Sleep(_intervalInSecond * 1000);
                mouse_event(NativeCall.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
                mouse_event(NativeCall.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
                count++;
            }
            Stop();
        }

        public void Stop()
        {
            _clickThread.Abort();
        }
    }
}

世間はRxやasync/awaitで盛り上がっていますが、今回に限っては昔からあるThreadでわかりやすくしてみました。起動されたら別スレッドでマウスクリックイベントを発行させます。マウスクリックはマウスのダウンとアップイベントの組み合わせで実現しています、ここもWindowsAPIを使っています。また、マウスクリックイベントは1s単位にしていますが、これは無駄にクリックしまくって制御不能になるのを避けているだけでちゃんとしたエラー処理ができるようになれば高○名人を超えるスーパークリッカーにすることももちろん可能です。

コメントを残す