Skip Navigation Linksホーム > ライブラリ > ボタン > ボタンの 2 度押しを防止する

ボタンの 2 度押しを防止する

言語フィルタ:

概要

ボタンの 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 メッセージ以外の全てのメッセージを削除しています。

展開されたイメージ 保留されているメッセージを削除する - Visual Basic
コピーイメージ コードのコピー
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
展開されたイメージ 保留されているメッセージを削除する - C#
コピーイメージ コードのコピー
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 回だけ発生します。

展開されたイメージ クリックされたボタンだけ 2 度押しを防止する - Visual Basic
コピーイメージ コードのコピー
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
展開されたイメージ クリックされたボタンだけ 2 度押しを防止する - C#
コピーイメージ コードのコピー
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 はボタンの上にマウスがある時のホバー色を解除するために処理しています。

展開されたイメージ Visual Basic
コピーイメージ コードのコピー
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
展開されたイメージ C#
コピーイメージ コードのコピー
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
    }
}