Excel VBA 第9回 ユーザーフォームとモジュールのデータ引き渡しタイミング

# はじめに
最近は、Excel VBAでプログラミングをしておらず、作成済みのものを使用していました。
ふとした用途でユーザーフォームを使用してみたところ、ユーザーフォームとモジュールのデータ引き渡しタイミングで悩んでしまったので、備忘として残しておこうかと思いました。
どうでもいいですが、QiitaのMarkDown記載変わったんですね。本当に投稿していなかったなと今更に気づきました。

# 今回実施する内容
ユーザーフォーム(モーダル)を使用して、そこに何かの値を設定し、「OK」ボタンを押したときに、その値を取得するものを作ります。
今回は、テキストボックスの値を取得して、OKボタンを押すとMsgBoxを開くものにします。

![UserForm.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/46022c56-8868-4741-1897-2941a7fb645c.jpeg)

その値をどこに設定すべきなのか方法は色々ありますが、あまりPublic変数は使いたくないため、関数の戻り値として取得する方法でおこないます。

# ソースコード(Git Hub)
[UserFormDataTransfer](https://github.com/Build-Mount/VBA_09_UserFormDataTransfer)

# 環境
OS:Windows 10 JP
Excel: Excel 2021 (64bit)

# 参考
[Show メソッド](https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/show-method)

# 用語
特になし

# 標準モジュールのソース

```VisualBasic:Module1.bas
Option Explicit

Public Sub FormDataTransfer()
    Dim strTextValues() As String
    Dim i As Long
    Dim strMsg As String
    
    strTextValues = UserForm1.GetTextValues
    
    For i = LBound(strTextValues) To UBound(strTextValues)
        strMsg = strMsg & strTextValues(i) & vbCrLf
    Next
    
    MsgBox strMsg

End Sub

```

特に説明すべきこともないのですが、UserForm1.GetTextValuesで、ユーザーフォームを表示させて、そこからの戻り値を`strTextValues`の配列に代入しています。
その後のFor分は単なる取得したデータをつなげて表示しているだけです。

# UserForm1の構成
ユーザーフォーム(UserForm1)は、今回のケースでは、テキストボックスを２つ作り(`TextBox1`と`TextBox2`)、OKボタン(`OKButton`)ときキャンセルボタン(`CancelButton`)を作るシンプルなものです。

- OKボタンを押すと、ユーザーフォームが閉じられ、２つのテキストボックスの値を応答します。
- Cancelボタンを押すと、ユーザーフォームが閉じられ、やはり２つのテキストボックスの値を応答します。
- 右上の「ｘ」ボタンを押すと、ユーザーフォームが閉じられ、やはり２つのテキストボックスの値を応答します。

# UserForm1のソース

```VisualBasic:UserForm1.frm
Option Explicit

Private strTextValues(1 To 2) As String


'OKボタンを押下したときに実行する。
Private Sub OKButton_Click()
    strTextValues(1) = TextBox1.Value
    strTextValues(2) = TextBox2.Value
    Me.Hide
End Sub

'Cancelボタンを押下したときに実行する。
Private Sub CancelButton_Click()
    Me.Hide
End Sub

'xボタンを押下したときに実行する。
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = vbFormControlMenu Then
        Cancel = True
        Me.Hide
    End If
End Sub


'モジュールから呼び出される。
'ユーザーフォームを開いて、テキストボックスに値を入れた後、OKボタン、もしくはCancelボタンを
'押下すると、そのテキストボックスの値を戻り値として返す。
Public Function GetTextValues() As String()
    Me.Show
    GetTextValues = strTextValues
End Function
```

`OKButton_Click`と`CancelButton_Click`のSub関数は、OKボタンを押したとき、Cancelボタンを押したときに実施されます。
OKボタンを押したときは、テキストボックスの値を取得して、`strTextValues`の配列に値を代入します。
その後、UserForm1を非表示にします。
Cancelボタンを押したときは、単にUserForm1を非表示にします。

`UserForm_QueryClose`のSub関数は、「ｘ」ボタンを押したときに実施されます。
`CloseMode`が`vbFormControlMenu`の場合は「ｘ」ボタンを押したときのため、`Cancel = True`として、`Me.Hide`でUserForm1を非表示にします。

`GetTextValues`の関数は、標準モジュールから呼び出され、UserForm1を表示し、UserForm1が閉じられると、strTextValuesの配列の値を応答します。

#データ引き渡しのタイミングについて
ユーザーフォームのデータ引き渡しで何に迷ったかといえば、２つありました。
- UserForm1を閉じたときにどうやって値を戻すのか？`UserForm1.Show`ってどうなると終わるのか？
- OKボタンやCancelボタンを押したときにSub関数が発生しますが、これはSub関数であって戻り値がないので、どうやって値を渡すのか？

久しぶりに作った時は？？？となって困ったんです。考えれば大したことはないのですが。
[Show メソッド](https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/show-method)をみると以下の記載があります。

>UserForm がモーダルの場合、ユーザーはアプリケーションの他の部分を使用する前に応答する必要があります。 UserForm が非表示またはアンロードされるまで、その後のコードは実行されません。 UserForm が表示されたとき、アプリケーション内のその他のフォームになっていますが、その他のアプリケーションは無効ではありません。

上記の通り、モーダルの場合`UserForm.Show`が非表示、アンロードされるまではその後のコードは実行されませんから、それが終わってからテキストの値を取り出せばいいんだなということでした。
UserFormがアンロードされる場合は、Public変数の`strTextValues`も消えてしまうため、`Unload`は使えないですね。したがって、`Me.Hide`で非表示にするようにしました。

また、OKボタンを押したとき、CanCelボタンを押したときについては、これもすでに上に記載した通りで、`Me.Hide`すれば、`UserForm.Show`が終わるわけですからそのあとに値を取得するだけでよかったのです。
「ｘ」ボタンを押したときも同じですね。

`GetTextValues`で`Me.Show`の後に`strTextValues`を取得してその値を応答します。
`GetTextValues`は、関数名で言えば値取得だけなのに、UserFormも表示させているため、思わしくないかもしれません。`UserForm.Show`はそれだけにして、あとからプロパティとして、`strTextValues`を取得するでもよかったかもしれません。
いずれにせよ、ポイントは、
- OKボタンを押したときに値を取得してPublic変数に保存しておく。
- `UserForm.Show`が終わったらそのPublic変数の値を取得する。
ってことかなと思いました。
`UserForm.Show`の動きを忘れなければ問題ないはずなんですが・・・

#おわりに
ユーザーフォームとモジュールのデータ引き渡しタイミングについて記載しました。
モードれすで実施すると、Me.Showで止まらずすぐさま`GetTextValues = strTextValues`が実行されてしまうため、問題です。まあなんとかすればいいんでしょう。

本ソースは、単なるポイントを記載したかっただけなので、MsgBoxがCancelでも表示されるとかは気にしていません。