【ExcelVBA】ModelessなフォームでSetFocus

ちょっと前にModelessなユーザフォームでエラーが出るという質問に回答したのですが、そのときこうすればよいなというやり方を追加で回答しようとしたら、もう締め切られていて書き込めなかったので(汗)、ここに書いておきます。

質問の内容

質問の内容は、下記のとおりです。2022年6月15日の質問でした。

エクセルユーザーフォームについて質問です。
vba画面(コードを入力する画面)で”ユーザーフォームを実行(F5)”と、
標準モジュールで呼び出したユーザーフォームで動きが違うのですが、
どうすればよろしいでしょうか?

具体例は
もし textbox1に日付が入れば true
日付じゃなければmsgbox”日付ではありません”と表示
※日付ではない数字または文字を選択(SetFocus)
としております。

これをvba画面で実行すると理想通りの動きをするのですが、
エクセルに挿入されているボタンでマクロ登録している標準モジュールで立ち上げたユーザーフォームを実行すると、
※日付ではない数字または文字を選択(SetFocus)
が動かずに、数字または文字を選択されない状態になってます。

実務で使おうとすると”日付ではありません”と表示されたが、
選択されていないので、マウスで選択してから入力し直す。と手間を減らしたいです。

説明がわかりずらいと思いますが、助言を頂けると幸いです。
宜しくお願い致します。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13263341278
https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13263341278

ちょっと解説すると、図の右下の入力フォーム表示というSubで、.Showの引数にvbModelessとあります。これは、モードレスにフォームを表示できるオプションです。モードレスというのは、表示されたフォームが頑固に画面に居座って、フォームの操作を完了しないとワークシートの操作に戻れない・・・なんてことがないモードのことです。

得てしてユーザフォームは頑固で厚かましいのですが、vbModelessをつけることで一転して謙虚で控えめな存在になります(個人の感想です)。

質問主様は何を困っていたかというと、フォームの中で日付を入力してもらいたいテキストボックスがありまして、そのテキストボックスで日付以外の値(たとえばsssssとかaaaaとか適当な文字列)が入力されたら、MsgBoxでエラーを表示して、テキストボックスの間違った文字列を選択して再度入力を促すということをしたいのに、なぜかうまくいかないということに困っていました。具体的に言うと、MsgBoxまではうまくいくのですが、そのあとの、本来であれば間違った文字列を選択してSetFocusする部分が沈黙する(全然機能しない)という症状が出ていました。

ユーザフォームのVBE上でデバッグモードで試すとうまくいくのに、本番さながら、標準モジュールから入力フォームを表示してテストするとうまくいかない、なぜだ?というご質問でした。

原因

原因は、MsgBoxのせいでフォーカスが失われている点です。

ModelessなフォームはWIndowsハンドルを見失いやすいのです。エラーメッセージにMsgBoxを使ってしまっているのでフォーカスを失っているのでした。

ModelessとSetFocusとは両方使うのは贅沢というもので、どちらかをあきらめないといけないなと個人的には思っています。VBAはそんな高度なことはできないのです。・・・いや、できるけど、そのためにはVBAでなくWinAPIとか、VBA以外の強力な助っ人を導入するしかないと思います。

そこで、私が回答したのは次のような内容でした。

回答

Modalessなユーザフォームに対するSetFocusはうまくいかないことが多いです。
この辺の記事が参考になるかも?
https://ameblo.jp/qoll/entry-11083672592.html

Modelessなユーザフォームに対して動作中に、MsgBoxを出してフォーカスを失っているので、その後またModelessなユーザフォームに戻ろうとしてもうまくいかないのかなあ?と思います。

解決策はちょっと思いつきません。↑の記事にあるような別のコントロールにいったんフォーカスを移してからTextBoxにSetFocusする方法も試してみたけど、私の環境ではうまくいきませんでした。

お使いのExcelバージョンでも症状が違うと思います。

https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13263341278

ここから追加で回答したかった内容

この回答をしてから思いついたのですが、MsgBoxをあきらめればいいんじゃね?と思いました。SetFocusもModelessもあきらめず、MsgBoxをあきらめて、その代わりフォームにラベルを追加して、ラベルにエラーメッセージを表示するのです。

こんな風に。

ラベルにエラーメッセージを表示するようにしたフォーム。

ラベルはユーザフォームの一部ですから、SetFocusは失われません。一応、デバッグとワークシートからの起動と両方試しましたが、フォーカスは失われませんでした。これでうまくいったかな?

コードはこんな感じ。

Private Function FP_DateExit(objTextBox As MSForms.TextBox) As Boolean
    Dim strDate As String
    Dim dteDate As Date
    Label2.Caption = ""
    strDate = Trim(objTextBox.Text)
    '日付なら再編集
    If IsDate(strDate) Then
        dteDate = CDate(strDate)
        objTextBox.Text = Format(dteDate, "yyyy/mm/dd")
        FP_DateExit = True
    Else
'        MsgBox "日付ではありません。", vbExclamation
        Label2.Caption = "日付ではありません"
        '全桁選択
        Call GP_AllSelect(objTextBox)
    End If
        
End Function

ラベルのエラー文字が残ってしまうので、もうちょっと細かい制御を追加しないといけないのですが、考え方としてはこれで分かってもらえると思います。

と言っても、今さらここに書いたところで、質問主様には届かない気がするのですが、別の誰かの役に立つかもしれないし、ここにさらしておきます。