社内SEの徒然なる日記

Excelで中止機能付きのマクロを作ってみた(フォーム編)

前回の問題

前回(Excelで中止機能付きのマクロを作ってみた)で、実行中のマクロを停止する機能を紹介したのですが、実は問題があります。

処理実行中に、適当なセルをクリックして文字を打ち込むと、マクロが終了してしまいます。
中止しなくても止まる

どうやら、DoEvents関数の実行時に何らかのイベントが発生した場合には、処理が終わってしまうようです。
上記の例では、シート内のセルを変更した時に発生する「Worksheet_SelectionChange」が動いたことが原因と思われます。

さて、これを何とか出来ないものだろうか?

試行錯誤

ダメもとで、いろいろ試して見ました。
例えば、Application.EnableEventsプロパティやApplication.ScreenUpdatingプロパティを駆使して制御できないか?とかですね。もちろん、ダメでしたけど。

他には、どっかに処理中の変数の情報とかを退避しておいて、処理が中断されたら中断された時点から再開できるようにするって手も考えたんですけど、それだと気軽に使えないからダメ。

...まっとうな方法で実現できそうになかったので、疑似的に実現する方法を考えてみます。
ようするに、マクロを実行している間はExcelに触れないって状況を作ってやればいいんですよ。

考えたのは、マクロ実行直後にユーザーフォームを表示して、Excelウィンドウをフォームの下に隠す。
マクロ終了後に、フォームを閉じて、Excelウィンドウをマクロ開始時のサイズに戻すっていう方法です。

サンプル

フォーム名は「macroStopForm」として、件数表示用のテキストボックスと中止用のボタンを配置します。
中止用フォーム

中止ボタン押下時の処理は、前回までと同様にPublic変数「gStopFlag」への設定とします。

' 中止ボタン押下時の処理
'
Private Sub macroStopButton_Click()

'処理中断フラグ = True:中止
gStopFlag = True

End Sub


シート上には、開始ボタンのみ設置します。
前回のカウンタと中止ボタンは、上記のフォームに機能を移動しています。
開始ボタン

開始ボタン押下時の処理は、下記のようになります。

' 処理メソッド
'
Public Sub macroStart()

'-----------
' 変数宣言
'-----------

'ループカウンタ
Dim i As Long

'Excelのウィンドウ状態
Dim myAppTop As Double
Dim myAppLeft As Double
Dim myAppWidth As Double
Dim myAppHeight As Double

'-----------
' 初期処理
'-----------

'Excelウィンドウを標準にします。
Application.WindowState = xlNormal

'処理中断フラグ = False:続行
gStopFlag = False

'現在のExcelウィンドウの状態を退避します。
myAppTop = Application.Top
myAppLeft = Application.Left
myAppWidth = Application.Width
myAppHeight = Application.Height

'フォームを読込ます。
Load macroStopForm

'フォームの位置を画面中央にします。
macroStopForm.StartUpPosition = 2

'フォームを表示します。
macroStopForm.Show (vbModeless)

'Excelウィンドウをフォームの下に隠します。
Application.Width = CInt(macroStopForm.Width)
Application.Height = CInt(macroStopForm.Height)
Application.Top = CInt(macroStopForm.Top)
Application.Left = CInt(macroStopForm.Left)

'-----------
' 処理
'-----------

'ループ
For i = 0 To 19

'1秒待機します。
Call K_StopTime(1)

'5回に一度、中止判定をします。
If i Mod 5 = 0 Then

'フォームの実行件数を更新
macroStopForm.countBox.Value = i + 1 & " / 20"

'OSに制御を返します。
DoEvents

'Excelウィンドウをフォームの下に隠します。
Application.Width = CInt(macroStopForm.Width)
Application.Height = CInt(macroStopForm.Height)
Application.Top = CInt(macroStopForm.Top)
Application.Left = CInt(macroStopForm.Left)

'中止判定
If gStopFlag Then
GoTo stopEnd
End If
End If
Next

'-----------
' 後処理
'-----------

'正常終了
normalEnd:
GoSub commonEnd
MsgBox "終了しました。" & Chr(10) & Chr(10) & "i = " & i
Exit Sub

'中止処理
stopEnd:
GoSub commonEnd
MsgBox "中止します。" & Chr(10) & Chr(10) & "i = " & i
Exit Sub

'終了時の共通処理
commonEnd:

'フォームを終了します。
Unload macroStopForm

'Excelウィンドウを開始時点の状態に戻します。
Application.Width = myAppWidth
Application.Height = myAppHeight
Application.Top = myAppTop
Application.Left = myAppLeft

Return

End Sub


解説

基本的なロジックは前回と同様ですので、まずは前回の記事をご覧下さい(下にリンクがあります)。
前回との変更点を解説します。

1.変数宣言

Excelの位置、サイズを保持する変数を準備します。
画面上の位置、縦横のサイズの4つが必要です。

2.初期処理

ここが、大きく変わった(追加した)箇所になります。

まず、Excelの表示状態を表す「Application.WindowState」プロパティに「xlNormal」を設定して標準ウィンドウにします(ちなみに、xlMaximaizedで最大化、xlMinimizedで最小化)。

次に、ApplicationオブジェクトのTop、Left、Width、Heightの値を、先ほど作成した変数に設定します。
処理終了時には、ここで保持した値からExcelウィンドウの状態を復帰します。

フォーム「macroStopForm」をloadステートメントで読み込みます。
まぁ、いきなりShowメソッドでフォームを表示しても良いんですけど、「まずフォームをロードして、加工してから表示する」っていう事を良くやるんで、フォーム使用時の私の癖になっちゃいました。

そして、フォームを画面中央に表示するために、「macroStopForm.StartUpPosition」プロパティに 2 を設定します。
なお、0は設定しない、1はExcel画面の中央、3で画面左上になります。

これで下準備が終わったので、Showメソッドでフォームを画面表示します。
この時に、Showメソッドの引数に「vbModeless」を設定してモードレスでフォームを表示します。
これを設定しないと、フォームが終了するまで次の処理が実行されません。
...このメソッドに、「処理は続行させるけどシートの操作は許さないって」オプションがあれば、こんな小細工はいらないんですけどね。

最後にフォームの画面上の位置、縦横のサイズをApplicationオブジェクトの対応するプロパティに設定してやります。これで、Excelが表示したフォームの影に隠れる事になります。
サンプルではCIntで整数型にしていますが、そのまま設定しても問題ありません。

3.処理

ここは、前回とほぼ同じです。
ループ内でカウントが5で割り切れる値になるたびに、DoEventsを実行して処理中止の判断を行います。

違いは、カウントの表示先がシートからフォームに変わった事と、DoEvents実行後にExcelウィンドウの位置とサイズの調整(フォームの下に隠す)をしている事くらいです。
位置調整をするのは、ユーザーにフォームの位置を移動された時に、Excelもそれにあわせて移動させるためです。

あと、処理が中止された場合、前回はそこで終了(Exit Sub)していましたが、今回は後処理があるので終了処理に遷移させています。

4.後処理

フォームを終了して、Excelの位置、サイズを処理開始時の状態に復元します。
これは正常時も、途中で中止した時も実行させます。

動かしてみよう

シート上の「開始」ボタンを押すと、Excelが画面から消えて、フォームのみが表示されているように見えるはずです。
処理中にフォームの「中止」ボタンを押下すると、カウンタが5の倍数になるタイミングで処理が中止されます。

ちなみに、今回、処理実行中の画面は付けませんでした。
性質上、実行画面を出すってことはデスクトップを表示しないといけないんですけど、これが汚すぎて人に見せられる状態じゃなかったからです。

油断すると、デスクトップってゴミ置き場になっちゃいません?
そろそろ整理しないとなぁ。

今さら言うことではないが...

ここまで作っといて言うのもなんですが、なんかヤクザな感じがするんですよね。
自分だけで使うツール用としては良いんですけど、他人に使ってもらうExcelには怖くて実装できません。

Excel自体の設定を弄ってるんで、環境によってどんな障害が発生するか分りません。
ありそうなのが「マクロ終了時にExcelウィンドウの位置情報が壊れて、画面外に消えてしまう」ですかね。

なんだかんだ言って、シートなりフォームなりに「処理実行中です。終了するまで操作しないで下さい。」ってメッセージ出すだけにした方が安心して配布できそうですね。

前回:Excelで中止機能付きのマクロを作ってみた
投稿記事の一覧:目次

--- blog end ---

スポンサードリンク

PageTop

コメント


管理者にだけ表示を許可する