言語フィルタ:
概要
ボタンの 2 度押しを防止する方法を紹介します。
対象コントロール
- System.Windows.Forms.Button
- System.Windows.Forms.ToolStripButton
解説
ボタンの 2 度押しを防止する二種類の方法を紹介します。
一つ目は、ボタンのクリック処理中に他のボタンやコントロールをクリックした場合でも、そのクリックを防止する方法です。
ボタンのクリック処理中に再度クリックされた場合、そのクリック情報はメッセージとしてキューに保留された状態になっています。クリック処理が終了した後に保留状態のメッセージが処理されるため、2 度押しの状態になります。保留されているメッセージを削除することで、ボタンの 2 度押しを防止することが出来ます。
保留されているメッセージを削除するには、PeekMessage 関数を使用します。PeekMessage 関数の第 5 引数に PM_REMOVE (1) を指定することで保留状態のメッセージを削除することが出来ます。WM_PAINT メッセージは PeekMessage 関数で削除できないため DispatchMessage 関数を使用して処理する必要があります。(PeekMessage 関数、DispatchMessage 関数は Win32API です。詳細は MSDN などを参照してください)
メッセージを削除するタイミングは、クリック処理の最後にします。ボタン毎の Click イベントの最後に、メッセージを削除してもいいですが、手間がかからないようにボタンの OnClick メソッドでメッセージを削除するサンプルを紹介します。このサンプルではクリックメッセージの他に WM_PAINT メッセージ以外の全てのメッセージを削除しています。
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;
}
}
}
二つ目は、クリックされたボタンだけ 2 度押しを防止する方法です。
ボタンのクリック処理中を表すフラグを一つ用意します。ボタンの Click イベントの最初にフラグが立っていたら処理を抜けるようにします。ボタンの Click イベントでフラグを立てます。フラグを降ろすタイミングは 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 イベント処理
}
ソースコード
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_LBUTTONUP
DispatchMessage(msg)
Case WM_MOUSELEAVE
DispatchMessage(msg)
Case WM_PAINT
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_LBUTTONUP:
DispatchMessage(out msg);
break;
case WM_MOUSELEAVE:
DispatchMessage(out msg);
break;
case WM_PAINT:
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
}
}