Adsenseリンクユニット

2013年5月17日金曜日

ボタン2度押しを防止するプログラミング技術

今日は、ボタンを連打した際に、ボタンイベントが何度も発生して困ったことはありませんか。
画面系のプログラミングを行なっていると必ずボタン2度押し対策をしなければいけないシュチュエーションに遭遇します。

具体的には、新規登録画面を作るとします。新規登録のボタンを実装した場合に、登録ボタンの処理が連続で2度動作してしまうと、データが2重に登録されてしまうことになります。これは致命的なエラーですよね。

なぜ、このような現象が起こるのか。画面系のプログラミングに慣れていない方は、疑問に思うかもしれません。このような現象を引き起こす原因は、画面系のプラグラムが、イベント駆動型と言われるイベントを元に動く仕組みをベースに動作しているからなのです。

ボタンを押すと、ボタンクリックイベントが発生します。ボタンを連打するとどうなるかというと、ボタンクリックイベントが何度も発生し、イベントを管理するイベントプールと言われる場所に溜まっていきます。溜まったイベントは、溜まった順番に順次処理されていきます。

画面プログラミングでは、イベントに対して実際に行いたい業務処理を実装します。今回の場合は、画面入力情報でデータを新規登録する処理を実装します。ところが、困ったことに、新規登録のボタンイベントが何度も繰り返されると、同一内容のデータが何度も登録されてしまう現象が起こってしまい致命的な問題となります。

ボタン連打によるイベントの連続動作は、何も考慮せずボタンクリックイベントを単純に実装しただけでは、必ずといっていい程同じ問題に遭遇することでしょう。

この問題を解決する方法は、2種類あります。第一の方法は、最初のボタンクリックイベント処理中に発生したイベントの処理をスキップさせる方法です。第二の方法は、ボタンクリックイベント処理が終了する際に、それまでに溜まったイベントをイベントプールから捨ててしまい、イベントそのものを発生させないようにする方法です。


ボタンイベント処理中に発生したボタンイベント処理をスキップする方法


サンプルソースです。

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 ObjectByVal e As System.EventArgs)
    Me.buttonProcessing = False
End Sub

Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click
    ' クリック処理中は処理を抜ける
    If Me.buttonProcessing = True Then
        Return
    End If

    Me.buttonProcessing = True

    ' Click イベント処理
End Sub

-参照サイト「IYouryellable」より-

ポイントを解説しますね。三行目のAddHandlerしているイベントがミソです。System.Windows.Forms.Application.Idleと書かれていますね。このイベントは、アプリケーションが処理を完了し、アイドル状態に入ろうとすると発生します。
つまり、ボタンクリックイベントが全て完了して処理するイベントがなくったタイミングに発生するイベントになります。

ボタンクリックイベントで、二重処理を防ぐフラグをONにし、ボタン連打で発生した不要なボタンクリックイベントが動作した際に、イベント処理の先頭でReturnし処理をスキップさせます。このフラグをOFFにするタイミングは、溜まったイベントがなくなったアイドル状態に突入したタイミングで発生するSystem.Windows.Forms.Application.IdleイベントでOFFにします。


フラグ変数を定義する必要があるので、フラグだらけになるのを嫌う方は、第二に方法があります。フラグによる判定は、実装が分り易いので好きな方は第一の手法で全く問題ありません。


イベント終了直前に、溜まった不要のイベントを捨ててしまう方法


サンプルソースです。
Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
    MyBase.OnClick(e)

    Dim msg As MSG
    While PeekMessage(msg, 000, PM_REMOVE)
        Select Case msg.msg
            Case WM_PAINT
                DispatchMessage(msg)
        End Select
    End While
End Sub
-参照サイト「IYouryellable」より-

非常にスッキリとした実装ですね。ボタンイベント処理が終了した後処理として、画面描画であるペイントイベント以外のイベントを捨てる処理を記述しています。

描画イベント以外のイベントを捨ててしまうと問題がある場合は、そのイベントもDispatchMessageさせてあげます。


どちらの方法でも良いと思います。オブジェクト指向に慣れている方は、当たり前に考えることですが、このような共通的に必要な動作は、各画面のイベント毎で実装してはいけません。

ボタンクラスを派生させた共通ボタン部品を作成し、共通ボタン部品の中で上記のどちらかの手法で実装を行うのが美しいですね。

同じ記述をソースの至ることろに散在させずに、共通化も考えましょう。

0 件のコメント:

コメントを投稿

スポンサードリンク