産業系・制御系システムを構築する時は、
- 間違えてワンクリックしただけで機械が動いてしまうと危ない
- かといって毎回警告のポップアップを出すのは煩わしすぎる
など、「ボタンの長押し」をトリガーにしたいことがよくあります。
三菱製GOTタッチパネル等には最初から長押し機能が入っていますが、昨今はWindowsフォームアプリケーションからロボットを制御したいというケースもよくあります。
ということで、Windowsフォームアプリケーションで長押しボタンを作る方法を解説します。
仕様としては
- ボタンを1秒間長押ししたら、機能を実行する
- 1秒以上押し続けても、2回目は実行されない
- 1秒未満でボタンを離せば、何も起こらない
とします。
サンプルコード
まず先に全体のサンプルコードを記載して、後から個別に解説します。
public partial class FrmMain : Form
{
//ボタン長押し用タイマ
private SystemThreading.Timer timer = null;
//ボタン長押し中フラグ
private bool flgPressing = false;
public frmMain()
{
InitializeComponent();
//ボタン長押し用タイマのコールバック登録
timer = new SystemThreading.Timer(new TimerCallback(Callback_BtnSample), null, Timeout.Infinite, Timeout.Infinite);
//コールバック関数
public void Callback_BtnSample(Object obj)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(BtnSample_LongClick));
}
else
{
BtnSample_LongClick();
}
}
//長押し成立時に実行したい機能
private void BtnSample_LongClick()
{
//任意の処理
}
//マウスが押され始めた時に起こるイベント
private void BtnSample_MouseDown(object sender, MouseEventArgs e)
{
//一秒後に一度だけ発動
timer.Change(1000, Timeout.Infinite);
flgPressing = true;
}
//マウスが離された時に起こるイベント
private void BtnSample_MouseUp(object sender, MouseEventArgs e)
{
//ボタンを離した時にタイマ無効化
timer.Change(Timeout.Infinite, Timeout.Infinite);
flgPressing = false;
}
}
}
次の項で、個別に解説していきます。
タイマとフラグの宣言
長押しを実現するために、Threading.Timerクラスを使用します。
また、ボタンが押されている状態を表すのにフラグも作っておきます。
「押している間ボタンの色を変える」などの制御がやりやすくなりますね。
//ボタン長押し用タイマ
private SystemThreading.Timer timer = null;
//ボタン長押し中フラグ
private bool flgPressing = false;
タイマのコールバック登録
タイマのコールバック関数を登録します。
ここでポイントとなるのが、第3引数dueTimeと第4引数PeriodをTimeout.Infiniteとしていること。
こうすることで、タイマは一旦、何も起動しない状態として設定されます。
//ボタン長押し用タイマのコールバック登録
timer = new SystemThreading.Timer(new TimerCallback(Callback_BtnSample),null,Timeout.Infinite,Timeout.Infinite);
↓Timer関数のパラメータ仕様は以下URLを参照
サブスレッドから扱うための「Invoke」
コールバック関数を見ると、わざわざもうワンクッション置いていることがわかります。
こんなことをしなくても正常に動きそうですが、実際に動かしてみると例外が発生して動かなくなります。
これは「異なるスレッドのオブジェクトを操作しようとしている」から。
Threading.Timerのイベント処理が動作するのはサブスレッド。
フォーム上のボタン等はメインスレッドで生成されています。
このようにスレッドを跨ぐ操作を行いたいときには、特別な手続きが必要です。
それがinvokeです。
- InvokeRequiredを使い、現在サブスレッドから処理が動いているか(Invokeの必要があるかどうか)を判断する
- 必要ならばInvoke()関数を使って、”実行したい機能”の関数ポインタを渡す
という流れになっています。
//コールバック関数
public void Callback_BtnSample(Object obj)
{
if (this.InvokeRequired)
{
this.Invoke(new Action(BtnSample_LongClick));
}
else
{
BtnSample_LongClick();
}
}
//長押し成立時に実行したい機能
private void BtnSample_LongClick()
{
//任意の処理
}
MouseDown/MouseUpイベントの登録
最後にマウスのイベントを作成します。
MouseDown時にタイマのdueTimeを1000ミリ秒に設定することで、ボタンが押されてから1秒後にイベントが発火します。
MouseUp時にタイマのdueTimeをInfiniteに戻すことで、1秒未満でボタンを離せば何も起こらないようにできます。
periodはずっとInfiniteなので、押しっぱなしでも何度もイベントが発火することはありません。
※FormのDesigner.csでイベントハンドラの追加をするのを忘れずに!
//マウスが押され始めた時に起こるイベント
private void BtnSample_MouseDown(object sender, MouseEventArgs e)
{
//一秒後に一度だけ発動
timerBtnSample.Change(1000, Timeout.Infinite);
PressingBtnSample = true;
}
//マウスが離された時に起こるイベント
private void BtnSample_MouseUp(object sender, MouseEventArgs e)
{
//ボタンを離した時にタイマ無効化
timerBtnSample.Change(Timeout.Infinite, Timeout.Infinite);
PressingBtnSample = false;
}
以上で
- ボタンを1秒間長押ししたら、機能を実行する
- 1秒以上押し続けても、2回目は実行されない
- 1秒未満でボタンを離せば、何も起こらない
という仕様の長押しボタンの完成です!