状況に応じてボタンの表示を変える


コマンドボタンに表示される文字列は、CommandButtonのCaptionプロパティで操作できます。次のコードは、ボタンをクリックするたびに、そのボタンの文字列を変更します。なお、ボタンの文字列は、デザイン段階で「実行」に設定しておいてください。

Private Sub CommandButton1_Click()
    With CommandButton1
        If .Caption = "実行" Then
            .Caption = "中止"
        Else
            .Caption = "実行"
        End If
    End With
End Sub

長い処理を中止する

こうした、ボタンの文字列変更は、ひとつのボタンを「実行」と「中止」に使い分けるときなどに便利です。たとえば、すごく時間のかかる処理があったとします。それを「実行」ボタンで開始するのですが、途中で処理を中止するようなとき、最初の「実行」ボタンが処理中は「中止」ボタンになっていれば、同じボタンを操作するだけで済みます。Windowsの一般的なUIでは、そういうのが多いですよね。以下に、そのやり方を解説します。

こんな感じにデザインします。たとえば、コマンドボタンで、次のような処理をします。

Private Sub CommandButton1_Click()
    Dim i As Long
    For i = 1 To Rows.Count
        Cells(i, 1) = i    ''恐ろしく時間のかかる処理...orz
    Next i
End Sub

もし、上の処理が一瞬で終わってしまうような高速なパソコンをお使いの方は、何か時間のかかる処理を考えてください。さて、まず処理を開始したときに、ボタンの文字列を変えます。

Private Sub CommandButton1_Click()
    Dim i As Long
    CommandButton1.Caption = "中止"
    For i = 1 To Rows.Count
        Cells(i, 1) = i
    Next i
    CommandButton1.Caption = "実行"
End Sub

しかし、これだけでは、ボタンの文字列が変わりません。最初の「.Caption = "中止"」を実行したあと、すぐに膨大な処理に突入してしまいましたので、ボタンの文字列変更が反映されないのです。こんなときは、DoEventsを実行して、制御をCPUに戻してやります。

Private Sub CommandButton1_Click()
    Dim i As Long
    CommandButton1.Caption = "中止"
    DoEvents
    For i = 1 To Rows.Count
        Cells(i, 1) = i
    Next i
    CommandButton1.Caption = "実行"
End Sub

UserFormを再描画するという意味では、Me.Repaintでもいいですね。

さて、ボタンの文字列が、処理前は「実行」で、処理中は「中止」なのですから

  • ボタンの文字列が「実行」だったら処理を開始する
  • ボタンの文字列が「中止」だったら処理を中止する

というようにします。途中で処理を中止するというのは、次のように考えます。

まず、ボタンがクリックされたら、ボタンの文字列(Captionプロパティ)を調べます。ボタンが[実行]だったら、そのまま長い時間のかかる処理に突入します。

長い時間のかかる処理では、本来の処理の途中でユーザーが[中止]ボタンをクリックしたかどうかを常にチェックします。もし、ユーザーが中止を選択したのなら、処理を中止します。

一方、ボタンがクリックされたとき、その文字列が[中止]だったら、[中止]ボタンがクリックされたことを長い処理に伝えます。そのために、ひとつ変数を用意します。ここではStopFlagとしました。

ボタンの文字列が[中止]だったら、[中止]ボタンがクリックされたのですから、そこで変数StopFlagにTrueをセットします。

長い処理は、本来の処理の途中で変数StopFlagがTrueかどうかを判定します。もしTrueだったら[中止]ボタンがクリックされたと判断して、処理を中止します。変数StopFlagがFalseだったら、[中止]ボタンはクリックされていないのですから、処理を続行します。

Dim StopFlag As Boolean
Private Sub CommandButton1_Click()
    Dim i As Long
    Select Case CommandButton1.Caption
    Case "実行"
        CommandButton1.Caption = "中止"
        Label1.Caption = "[中止]ボタンで中止します"
        DoEvents
        For i = 1 To Rows.Count
            Cells(i, 1) = i
            DoEvents
            If StopFlag = True Then Exit For
        Next i
        CommandButton1.Caption = "実行"
        Label1.Caption = ""
        StopFlag = False
    Case "中止"
        If MsgBox("中止しますか?", vbYesNo) = vbYes Then StopFlag = True
    End Select
End Sub

ここでのポイントはFor Next内のDoEventsです。DoEventsステートメントは、制御をWindowsに解放するステートメントです。非常に時間のかかる長いFor Nextなどでは、そのループが終わるまで、VBAがCPUを乗っ取ってしまい、他の処理ができなくなることがあります。そうなると、ユーザーが[中止]ボタンをクリックしたというイベントさえ、For Nextの中に割り込めなくなります。そうならないために、時間のかかるFor Next中でDoEventsを実行して、瞬間的に制御をWindowsへ解放してやります。パソコンの環境にもよりますが、今回のような割り込み処理では、こうしたDoEventsは必須です。