[ExcelVBA] UserFormの×(閉じる)ボタンへの対策方法いろいろ

[ExcelVBA] UserFormの×(閉じる)ボタンへの対策方法いろいろ

ユーザーフォームから処理を実行させたいとき、×ボタンを押されたときの検出方法ってないのかなーと思い立ったところから、方法をまとめてみたtipsです。


背景

このようなUserFormを作ったとして。

131129-1

ボタンをふたつ作って、それぞれこういう役割をさせたいとします。

Private Sub CommandButton1_Click() 'OKボタン
  'ここを押された時だけ処理を行いたい
End Sub

Private Sub CommandButton2_Click() 'Cancelボタン
  '終了させる
End Sub

で、実際使う人に動作確認してもらったら、ちょっと気づいたことがありました。処理を中断したいとき、PCに苦手な人ほど、用意した「Cancel」ボタンを押さずに右上の×ボタンを押してしまうんですよね。

理由を聞いてみたら、「キャンセルを選ぶのもなんか怖い」とか「よくわかんないときは×を押しちゃう」というような。怖いのか。うーん悩ましい。

で、こんなツイートをしてたら親切に教えて頂けました!@riatwさん、@Mako_Misakiさん、非常に助かりました♡ありがとうございますー!

せっかくなので、今まで使ったことのある対策方法を含めて、いくつかある手段をまとめてみます。

追記:推奨案

この記事を書いた当時、「可読性の良い」「メンテナンス性の良い」プログラミングというものがイマイチわかってなくて、×ボタンがクリックされたか検出してみたり、Publicなフラグを持たせてみたり、×ボタン消してみちゃったり、なんかいろいろ頑張ってましたが、今更考えると作法の良いものではありませんでした。

こちらの記事に書きましたが、まずは命名規則が大事! プロシージャは動詞+名詞の「何をするのか想像がつく」名称にしたり、モジュールやフォームもデフォルトのModule1、UserForm1のままにしておかないで、目的を表す名称にしておくこと。それと、適切な名称を付けたモジュールやフォームの中身はそれに関する記述だけにして、なんでもかんでも詰め込まないこと。

あと、Formは値のチェックと受け渡しをする場所であって、処理は分離してたほうが良いだろうな思うようになりまして、今回の件は、以下のように書くのが一番いいんじゃないかなと。

MainForm(UserForm)

Private Sub CommandButton1_Click() 'OKボタン
  '入力値などがあればここでチェック
  Call runTask '処理を走らせる
End Sub

Private Sub CommandButton2_Click() 'Cancelボタン
  Unload Me'フォームの破棄
End Sub

Task(Module)

Sub showMainForm()
  MainForm.Show 'フォームを開く
End Sub

Sub runTask()
  '処理
End Sub

以下、ちょっと恥ずかしいですが、何かの役に経つかもしれないので、当時書いた内容を残しておきます。

案1.QueryCloseイベントで×ボタン検出

Module1

Sub test()
  UserForm1.Show 'フォームを開く
  'ここに処理を書く
End Sub

UserForm1

Private Sub CommandButton1_Click() 'OKボタン
  Me.Hide 'フォームを閉じる
End Sub

Private Sub CommandButton2_Click() 'Cancelボタン
  End 'プログラムの実行を終了
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer) 'Formが閉じるとき
  If CloseMode = 0 Then '×ボタンを押された場合
    End 'プログラムの実行を終了
  End If
End Sub

今回教えて頂いた方法です。QueryCloseイベントを使って×ボタンを検出、その場合は終了させます。(Me.Hideとか Unload Meなどの、Formを閉じるだけだとModuleに戻って処理に進んでしまうので、Endで終了させてしまいます。)キャンセルボタンも同様に。

案2.Public変数で条件分け

Module1

Public flg As Boolean 'モジュールをまたいでも使える変数

Sub test()
  UserForm1.Show 'フォームを開く
  If flg = False Then Exit Sub 'フラグが立ってなければ終了
  'ここに処理を書く
End Sub

UserForm1

Private Sub CommandButton1_Click() 'OKボタン
  flg = True 'フラグ立てる
  Me.Hide 'フォームを閉じる
End Sub

Private Sub CommandButton2_Click() 'Cancelボタン
  Me.Hide 'フォームを閉じる
End Sub

Public変数でフラグを用意しておいて、OKボタンを押された時だけフラグを立てておく。Formが閉じられてModuleに戻ってきたときにフラグが立っていなかったら終了、のような感じ。QueryCloseを知らなかった時はこんな方法で書いてましたw

変数宣言の種類についてはこちらをご参照ください。

案3.OKボタンのClickイベントに処理を書く

Module1

Sub test()
  UserForm1.Show 'フォームを開く
End Sub

UserForm1

Private Sub CommandButton1_Click() 'OKボタン
  'ここに処理を書く
End Sub

Private Sub CommandButton2_Click() 'Cancelボタン
  Me.Hide 'フォームを閉じる
End Sub

これなら、OKボタンを押されたときだけ処理が走るので、キャンセルボタンと×ボタンは、ただフォームが閉じるだけになります。処理が長い場合や、いろんなところから使い回したい場合はModuleにSubプロシージャをつくっておいてCallで呼び出せばいいかなと。

案4.×ボタンを非表示に

Module1

'スタイルを取得する定数
Public Const GWL_STYLE As Long = -16
'ウィンドウスタイル
Public Const WS_SYSMENU As Long = &H80000
                                   
'API宣言
'ウィンドウハンドルを取得する関数
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
'ウィンドウに関する情報を返す関数
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
'ウィンドウの属性を変更する関数
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
'メニューバーを描画する関数
Public Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long

Sub test()
  UserForm1.Show
End Sub

UserForm1

Private Sub UserForm_Initialize()
  Dim hWnd As Long
  Dim lngWstyle As Long

  'ユーザーフォームのハンドル
  hWnd = FindWindow(vbNullString, Me.Caption)
  lngWstyle = GetWindowLong(hWnd, GWL_STYLE)

  '閉じるボタンの消去
  SetWindowLong hWnd, GWL_STYLE, lngWstyle And (Not WS_SYSMENU)
  'メニュー再描画
  DrawMenuBar hWnd
End Sub

そもそも×ボタンをなくしちゃえば押されることもあるまい!という方法。

こちらを参考にさせていただきました!
131129-2

走らせてみると、こんな感じ。(2007以降では未確認です。)

追記:2013でも同じように動きました!

いかがでしたでしょうか。個人的には1,3あたりが好きな気がします。どなたかの参考になれたら幸いですー!

公開日:2013/11/29
更新日:2017/06/19

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

コメントは承認制ですので、反映までしばらくお待ち下さい。(稀にスパムの誤判定にて届かないこともあるようですので、必要な際はお問い合わせからお願い致します。)