概要
ボタンの 2 度押しを防止する方法を紹介します。
対象コントロール
- System.Windows.Forms.Button
- System.Windows.Forms.ToolStripButton
解説
ボタンの 2 度押しを防止する二種類の方法を紹介します。
1. クリックされたボタンのみ 2 度押しを防止する方法
この方法ではボタンのクリック処理中にそれ以外のコントロールをクリックすると、そのコントロールは処理終了後にクリックされます。それも防止したいなら次の項目の方法を参照してください。
ボタンの 2 度押し判定用にフラグを用意します。ボタンがクリックされたらフラグを立てクリック処理をします。フラグが立っていたら処理を抜けます。Application.Idle イベントでフラグをおろします。
Application.Idle イベントは、簡単に説明すると他のイベントが全て終了した後に毎回発生します。Click イベントの中に再度クリックした場合は、全ての Click イベントが終了したら 1 度だけ発生します。今回の処理を行うのにベストなタイミングで発生するイベントです。
Private buttonProcessing As Boolean
Public Sub New()
AddHandler System.Windows.Forms.Application.Idle, AddressOf Application_Idle
End Sub
Private Sub Application_Idle(ByVal sender As Object, ByVal e As System.EventArgs)
Me.buttonProcessing = False
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
' クリック処理中は処理を抜ける
If Me.buttonProcessing = True Then
Return
End If
Me.buttonProcessing = True
' Click イベント処理
End Sub
private bool buttonProcessing;
public Form1() // Form のコンストラクタ {
System.Windows.Forms.Application.Idle += new EventHandler(Application_Idle);
}
private void Application_Idle(object sender, System.EventArgs e) {
this.buttonProcessing = false;
}
private void button1_Click(object sender, System.EventArgs e) {
// クリック処理中は処理を抜ける
if (this.buttonProcessing == true) {
return;
}
this.buttonProcessing = true;
// Click イベント処理
}
このフラグを他のコントロールと共有すれば、それぞれのコントロール間で 2 度押しを防止できます。しかしそれをやると Click イベントがフラグだらけになってしまうため、その場合は次の方法をお勧めします。
2. ボタンのクリック処理中に他のコントロールも含めて、全てのクリックを防止する方法
ボタンをクリックした時にそれがイベントとして発生する前に、メッセージが送られてきます。メッセージが処理されるとイベントが発生します。クリック処理中に再度クリックした場合などはメッセージがキューに溜まっていきます。クリック処理が終了した後にキューにメッセージがあればそれが処理されるため、2 度押しの原因になります。つまりクリック処理の最後に保留されているメッセージを全て削除すれば、2 度押しを防止できます。
保留されているメッセージを削除するには、PeekMessage 関数を使用します。PeekMessage 関数の第 5 引数に PM_REMOVE (1) を指定することで保留状態のメッセージを削除できます。WM_PAINT メッセージは PeekMessage 関数で削除できません。削除されると描画がおかしくなるため、WM_PAINT メッセージは削除しないで DispatchMessage 関数で処理します。(PeekMessage 関数、DispatchMessage 関数は Win32API です。詳細は MSDN などを参照してください)
メッセージを削除するときは Click イベントを使用するか OnClick メソッドをオーバーライドします。
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
MyBase.OnClick(e)
Dim msg As MSG
While PeekMessage(msg, 0, 0, 0, PM_REMOVE)
Select Case msg.msg
Case WM_PAINT
DispatchMessage(msg)
End Select
End While
End Sub
protected override void OnClick(EventArgs e)
{
base.OnClick(e);
MSG msg;
while (PeekMessage(out msg, 0, 0, 0, PM_REMOVE))
{
switch (msg.msg)
{
case WM_PAINT:
DispatchMessage(out msg);
break;
}
}
}
ソースコード
Button コントロールの 2 度押しを防止するサンプルを紹介します。
このサンプルでは「ボタンのクリック処理中に他のコントロールも含めて、全てのクリックを防止する方法」を使用しています。
2 度押しを許可するかどうかを示す AllowDoubleClick プロパティを追加しています。
WM_PAINT メッセージは削除しないで処理しています。WM_LBUTTONUP と WM_MOUSELEAVE はボタンの上にマウスがある時のホバー色を解除するために処理しています。
Imports System
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Namespace Extentions
Public Class DoubleClickPreventionButton
Inherits System.Windows.Forms.Button
Public Sub New()
Me._AllowDoubleClick = True
End Sub
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
MyBase.OnClick(e)
' 2 度押しを許可しない
If Me.AllowDoubleClick = False Then
Dim msg As MSG
While PeekMessage(msg, 0, 0, 0, PM_REMOVE)
Select Case msg.msg
Case WM_PAINT, WM_LBUTTONUP, WM_MOUSELEAVE
DispatchMessage(msg)
End Select
End While
End If
End Sub
Private _AllowDoubleClick As Boolean
<Category("動作")> _
<DefaultValue(True)> _
<Description("2 度押しを許可するかどうかを示します。")> _
Public Property AllowDoubleClick() As Boolean
Get
Return Me._AllowDoubleClick
End Get
Set(ByVal value As Boolean)
Me._AllowDoubleClick = value
End Set
End Property
#Region " P/Invoke "
Private Const PM_NOREMOVE As Integer = 0
Private Const PM_REMOVE As Integer = 1
Private Const WM_PAINT As Integer = &HF
Private Const WM_LBUTTONUP As Integer = &H202
Private Const WM_MOUSELEAVE As Integer = &H2A3
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function PeekMessage( _
ByRef lpMsg As MSG, _
ByVal hWnd As Integer, _
ByVal wMsgFilterMin As Integer, _
ByVal wMsgFilterMax As Integer, _
ByVal wRemoveMsg As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function DispatchMessage( _
ByRef lpMsg As MSG) As IntPtr
End Function
<StructLayout(LayoutKind.Sequential)> _
Public Structure MSG
Public hWnd As IntPtr
Public msg As Integer
Public wParam As IntPtr
Public lParam As IntPtr
Public time As Integer
Public pt As POINTAPI
End Structure
<StructLayout(LayoutKind.Sequential)> _
Public Structure POINTAPI
Public x As Integer
Public y As Integer
End Structure
#End Region
End Class
End Namespace
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Extentions
{
class DoubleClickPreventionButton : System.Windows.Forms.Button
{
public DoubleClickPreventionButton() {
this._AllowDoubleClick = true;
}
protected override void OnClick(EventArgs e) {
base.OnClick(e);
// 2 度押しを許可しない
if (this._AllowDoubleClick == false) {
MSG msg;
while (PeekMessage(out msg, 0, 0, 0, PM_REMOVE)) {
switch (msg.msg) {
case WM_PAINT:
case WM_LBUTTONUP:
case WM_MOUSELEAVE:
DispatchMessage(out msg);
break;
}
}
}
}
private bool _AllowDoubleClick;
[Category("動作")]
[DefaultValue(true)]
[Description("2 度押しを許可するかどうかを示します。")]
public bool AllowDoubleClick {
get { return this._AllowDoubleClick; }
set { this._AllowDoubleClick = value; }
}
#region P/Invoke
private const int PM_NOREMOVE = 0;
private const int PM_REMOVE = 1;
private const int WM_PAINT = 0xF;
private const int WM_LBUTTONUP = 0x202;
private const int WM_MOUSELEAVE = 0x2A3;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PeekMessage(
out MSG lpMsg,
int hWnd,
int wMsgFilterMin,
int wMsgFilterMax,
int wRemoveMsg
);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr DispatchMessage(
out MSG lpMsg
);
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
public IntPtr hWnd;
public int msg;
public IntPtr wParam;
public IntPtr lParam;
public int time;
public POINTAPI pt;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINTAPI
{
public int x;
public int y;
}
#endregion
}
}