2023/01/09

ユーザーフォームへの値の渡し方・戻し方




ユーザーフォームは、ユーザーに入力や選択を促すツールとして良く使われます。常に同じフォームが起動することもありますが、セルの値などにより「その状況に合わせたフォーム」が起動することが多いと思います。
状況に合わせたフォームにする為には、フォームを起動する際に状況に合わせた「値」を渡してあげる必要があります。
今回は「ユーザーフォームへの値の渡し方・戻し方」を紹介します。

1.概要

今回は、「ユーザーフォームへの値の渡し方・戻し方」を図1のように5種類紹介します。なお、図1で〇〇式と名前を付けていますが一般的なものでは無く、私が勝手に名付けたものですので悪しからず。

ユーザーフォームへの値の渡し方
図1


1番目は「共通変数渡し式」です。フォームを起動する側である標準モジュール(シートモジュールやクラスモジュールの場合もあります)とフォームモジュールの間で「共通して使用できるPublic変数」を使う方法です。
2番目の「モジュール変数渡し式」は1番目と似ていますが、値をやり取りするPublic変数がフォーム上に存在する方法です。
3番目は「セル渡し式」です。「ワークシート上のセル」を使って、標準モジュールとフォームモジュールを繋ぐ方法です。
4番目は「フォーム記述式」で、標準モジュールから「フォーム上に値を直接書き込む」方法です。フォームから受け取る場合も、標準モジュールからフォーム上に書かれた値を読み取ります。
5番目は「引数渡し式」で、フォームモジュール上に置いた「Functionプロシージャ」を呼び出します。呼び出す際に値を引数として渡し、フォーム内での処理後に標準モジュール側は戻り値として値を受け取ります。

なお、渡し時と受け取り時で異なる手法を使うことも可能ですし、複数の手法で受け渡しをしてもOKです。但し今回は、分かりやすくする為に、同一手法での受け渡しを行っています。

上記5つの動きを確認するため、今回は図2のような簡単なシステムを作りました(「サンプルファイル」)。
値受け渡しの流れ
図2


シート上のボタンをクリックすることでユーザーフォームが起動し、A1セルの値がフォーム上の左端に表示されます。
フォーム内の処理は、テキストボックスに値を入力することで掛け算を行い、その結果を右端に表示します。
フォームを閉じると、その掛け算の結果がメッセージボックスに表示される というものです。

このシステムの中では、標準モジュール側とユーザーフォーム側で2回の受け渡しがあります。
1回目は「標準モジュール側からフォームへ値を渡す」部分で、渡した値(A1セルの値)はフォームの左端に表示されます。
2回目は「フォームから標準モジュールへ値を戻す」部分で、戻された結果(フォームで計算した結果)は、メッセージボックスに表示させます

なお今回は「閉じるボタン」を設けていないので、ダイアログ右上×印で閉じて下さい。

2.ワークシートの準備(Sheet1)

サンプルファイル」では、図3のようにシート上にボタンを並べ、クリック時に実行されるマクロとして、標準モジュールのSUBプロシージャ「Button1」「Button2」・・・を上から順に登録しています。
なお、各ボタンの表面Captionは、配置時に手動で書き換えています。
実行ボタンの配置
図3


また黄色の背景色を付けた「A1セル」の値を、ユーザーフォームに渡すことにしました。

3.フォームのレイアウト

フォームのレイアウトは図4のようにしました。なお図4はUserForm1のものですが、他の方法で使用するフォームもレイアウトは同じですので省略しています。
実行ボタンの配置
図4


フォーム上には、「Labelを2つ」+「TextBoxを1つ」+「Labelを2つ」並べて配置します。
図4で分かる通り「初期値 × ユーザー入力値 = 計算結果」という計算式になるように、左端の1つ目のLabelは標準モジュールからフォームへ渡された値を初期値として書き込みます。2つ目のLabelには「×」をフォーム作成時に書き込み、3つ目はユーザーが入力できるようにTextBoxとします。
TextBoxの後ろのLabelには「=」を書き込み、一番右側のLabelには「掛け算の計算結果」をマクロ側から書き込みます。この計算結果をフォームから標準モジュールへの戻り値としています。

4.共通変数渡し式

4-1.標準モジュール

1番目のボタンをクリックした時に呼び出されるのが、図5のButton1プロシージャです。なお標準モジュールとフォームを繋ぐ役目をする共通変数「Val1」を、図5の01行目でPublic宣言しておきます。
  1. '========== ⇩(1) 共通変数宣言 ============
  2. Public Val1 As Long
  3. '========== ⇩(2) ボタン1による呼び出し ============
  4. Sub Button1()
  5.  Val1 = CLng(Val(Range("A1").Value))
  6.  UserForm1.Show vbModal
  7.  MsgBox Val1
  8. End Sub
図5


05行目「Val1 = CLng(Val(Range("A1").Value))」では共通変数Val1に、A1セルの値(図3では「100」)を代入します。A1セルに数値では無く文字列が入力されている場合は、Val関数で「ゼロ」に変換し、A1セルに小数点のある数値が入力されている場合はCLng関数で整数値に変換しています。
これは01行目で「Val1変数をLong型で宣言」しているための処理であり、他のデータ型の場合には、それに合わせた処理が必要です。
06行目「UserForm1.Show vbModal」では、UserForm1をモーダル(フォームを開いている間は、フォーム以外の操作が不可)で起動します。この行の実行後は図6のコードが実行され、フォーム内処理が終了(UserForm1を閉じる)した後、07行目「MsgBox Val1」が実行されます。フォーム内で書き換えられた「共通変数Val1」の内容をメッセージとして表示します。
寄り道
もしフォームをモードレスで起動してしまうと、06行目でフォームを開く指示をした後は、制御は「07行目のコード」に移動してしまうことになります。すると、フォーム内の処理が終わらずVal1の値が書き換えられていないので、メッセージとしては「A1セルの値のまま」が表示されることになってしまいます。

また「フォームをモードレスで起動」→「MsgBoxなどでユーザーの動作を待つ」という順番でコードを実行すると、フォームが起動した状態の上にMsgBoxが開くので、当然ながらそのMsgBoxを閉じないとフォームの操作は出来ません。
と同時に、フォーム起動時に発生するはずの「Initialize → Activate」イベントが、「Initialize」のみになってしまいます。そのため今回のように「Activateイベント」に記載したコードは実行されない為、Label類は文字列のままとなってしまい、MsgBoxを閉じた後でフォームを操作するとエラーが発生します。

この現象の原因をうまく説明することはできないのですが、「フォームをモードレスで起動」するコードと「MsgBox」のコードの間に「DoEvents」を1つ入れることで「Activateイベントが発生」してくれます。

もしモードレスでフォームを起動し、その直後にMsgBoxなどを表示させたい場合は、フォームの初期設定はInitializeイベントプロシージャに記載するか、DoEventsを間に入れるかすると良さそうです。

4-2.フォームモジュール

図5の06行目によって起動されたフォーム(UserForm1)のモジュールに記述されているコードが図6です。
フォームが起動されると「Initializeイベント」→「Activateイベント」の順に実行されますが、今回はInitializeイベントプロシージャが有りませんので、21行目のActivateイベントプロシージャが最初に実行されることになります。
  1. '========== ⇩(3) フォーム表示時の初期設定 ============
  2. Private Sub UserForm_Activate()
  3.  Me.Label1.Caption = Val1
  4.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  5. End Sub
  6. '========== ⇩(4) TextBoxの値を変更した時 ============
  7. Private Sub TextBox1_Change()
  8.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  9.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  10. End Sub
  11. '========== ⇩(5) フォームを終了する時 ============
  12. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  13.  Val1 = CLng(Me.Label4.Caption)
  14. End Sub
図6


21~24行目のActivateイベントプロシージャでは、まず22行目「Me.Label1.Caption = Val1」で、標準モジュールから渡された値(共通変数Val1)をLabel1に書き込みます。
そして23行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」で、フォーム上に書かれた計算式「初期値 × ユーザー入力値」を計算し、計算結果をLabel4に書き込みます。
なおActivateイベントが発生した時点では「TextBox1は空欄」ですので、「CLng(Val(Me.TextBox1.Value))」での処理結果はゼロとなり、よって計算結果(Label4)もゼロとなります。

ユーザーがTextBox1に値を入力(全角で入力した際には、Enterキーを押した時)すると、26行目のChangeイベントが発生します。Changeイベントプロシージャ内では、まず27行目「Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)」で、入力した値を「半角文字列」に変換します。これは、28行目の式中のVal関数が「全角数字=文字列=ゼロ」と判断してしまうためです。
この処理の代わりに、Activateイベント内等で「Me.TextBox1.IMEMode = fmIMEModeDisable」と全角での入力ができない様にしてもOKです。

28行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」は、23行目のコードと全く一緒でフォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

フォームを終了(今回の場合は、ダイアログ右上×印をクリック)時には、31行目のQueryCloseイベントプロシージャが呼び出されます。
終了時には、32行目「Val1 = CLng(Me.Label4.Caption)」で、計算結果であるLabel4の値を共通変数Val1に代入しています。このVal1の値が標準モジュールに戻される形になり、図5の07行目でメッセージとして表示されることになります。

なお32行目の代わりに、23行目・28行目の後ろで「Val1 = CLng(Me.Label4.Caption)」を実行し、初回及びTextBox1の値を変更するたびに共通変数Val1を置き換える方法でもOKです。

5.フォーム変数渡し式

5-1.標準モジュール

2番目のボタンをクリックした時に呼び出されるのが、図7のButton2プロシージャです。
  1. '========== ⇩(6) ボタン2による呼び出し ============
  2. Sub Button2()
  3.  UserForm2.Val2 = CLng(Val(Range("A1").Value))
  4.  UserForm2.Show vbModal
  5.  MsgBox UserForm2.Val2
  6. End Sub
図7


42行目「UserForm2.Val2 = CLng(Val(Range("A1").Value))」では、A1セルの値(図3では「100」)をUserForm2上のPublic変数Val2に代入します。フォーム上の変数なので、変数名の前に所在として「UserForm2.」を指定する必要があります。

43行目「UserForm2.Show vbModal」では、UserForm2をモーダルで起動します。この行の実行後は、図8のコードが実行されます。
UserForm2が閉じられた後に44行目「MsgBox UserForm2.Val2」が実行され、フォーム内で書き換えられた「フォーム変数Val2」の内容をメッセージとして表示します。

5-2.フォームモジュール

図7の43行目によって起動されたフォーム(UserForm2)のモジュールに記述されているコードが図8です。
  1. '========== ⇩(7) フォーム変数宣言 ============
  2. Public Val2 As Long
  3. '========== ⇩(8) フォーム表示時の初期設定 ============
  4. Private Sub UserForm_Activate()
  5.  Me.Label1.Caption = Val2
  6.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  7. End Sub
  8. '========== ⇩(9) TextBoxの値を変更した時 ============
  9. Private Sub TextBox1_Change()
  10.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  11.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  12. End Sub
  13. '========== ⇩(10) フォームを終了する時 ============
  14. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  15.  Cancel = True
  16.  Val2 = CLng(Me.Label4.Caption)
  17.  Me.Hide
  18. End Sub
図8


図7の42行目でフォームレベル変数Val2に値が代入されると、まずフォームが生成され、Initializeイベントが発生した後で、変数Val2に値が代入されます。今回はInitializeイベントプロシージャが有りませんので、42行目でフォームを起動すると、変数Val2に値が代入された後に最初にActivateイベント(54行目)が呼び出されます。

Activateイベントプロシージャ内では55行目「Me.Label1.Caption = Val2」で、既に値が代入されている変数Val2の値を計算式の先頭(Label1)に初期値として書き込みます。
56行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」で、フォーム上に書かれた計算式「初期値 × ユーザー入力値」を計算し、計算結果をLabel4に書き込みます。

ユーザーがTextBox1に値を入力すると、60行目のChangeイベントが発生し、まず61行目「Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)」で入力した値を「半角文字列」に変換します。
62行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

フォームを終了時には、66行目のQueryCloseイベントプロシージャが実行されます。
今回の仕様は、計算結果を保存した変数Val2は「フォームレベル変数」であるため、「フォームをUnloadすると値が消えてしまう」ことになります。なので、フォームはUnloadでは無く「Hideメソッド」で閉じる(≒隠す)必要があります。
そこで、QueryCloseイベントが通常通り実行されてしまうとフォームがUnloadしてしまいますので、67行目「Cancel = True」で、引数のCancelを有効(=終了できなくする)にします。

68行目「Val2 = CLng(Me.Label4.Caption)」では、計算結果であるLabel4の値をフォームレベル変数Val2に代入します。
最後に69行目「Me.Hide」で、フォームを閉じ(≒隠し)ます。「フォームを隠している=フォームは残っている」ために、フォームレベルの変数Val2の値を標準モジュール側から読み取ることができ、図7の44行目でメッセージとして表示できることになります。

6.セル渡し式

6-1.標準モジュール

3番目のボタンをクリックした時に呼び出されるのが、図9のButton3プロシージャです。
なおフォーム→ワークシートへ値を戻すセル位置は、今回は「A6セル(3番目ボタンの横の位置)」としました。実際のアプリでは意味のある位置にすることが多いと思いますが、逆に(ボタンの裏に隠したり、Fontの文字色を背景色と同じにしたりして)「ユーザーには見えない」ようにする場合もあります。
  1. '========== ⇩(11) ボタン3による呼び出し ============
  2. Sub Button3()
  3.  UserForm3.Show vbModal
  4.  MsgBox Range("A6").Value
  5. End Sub
図9


82行目「UserForm3.Show vbModal」では、UserForm3をモーダルで起動します。この行の実行後は、図10のコードが実行されます。
UserForm3が閉じられた後で83行目「MsgBox Range("A6").Value」が実行され、フォーム側から書き換えたA6セルの値をメッセージとして表示します。

6-2.フォームモジュール

図9の82行目によって起動されたフォーム(UserForm3)のモジュールに記述されているコードが図10です。
  1. '========== ⇩(12) フォーム表示時の初期設定 ============
  2. Private Sub UserForm_Activate()
  3.  Me.Label1.Caption = CLng(Val(Sheets("Sheet1").Range("A1")))
  4.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  5. End Sub
  6. '========== ⇩(13) TextBoxの値を変更した時 ============
  7. Private Sub TextBox1_Change()
  8.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  9.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  10. End Sub
  11. '========== ⇩(14) フォームを終了する時 ============
  12. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  13.  Sheets("Sheet1").Range("A6").Value = CLng(Me.Label4.Caption)
  14. End Sub
図10


フォームが起動されると91行目のActivateイベントプロシージャが呼び出されます。
92行目「Me.Label1.Caption = CLng(Val(Sheets("Sheet1").Range("A1")))」では、標準モジュールから渡される値は、ワークシート上のセル(今回はA1セル)を通して受け取り、フォーム上の計算式の先頭(Label1)に書き込みます。
なお今回は、値がA1セルに最初から入力されているので「標準モジュールから渡された」印象が薄くなってしまいましたが、図9の82行目のフォームを起動する直前に「A1セルに、フォームに渡す値を書き込んだ」と想像して頂ければ良いかと思います。
93行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

ユーザーがTextBox1に値を入力すると、97行目のChangeイベントが発生し、まず98行目「Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)」で、ユーザーが入力した値を「半角文字列」に変換します。
99行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

フォームを終了時には、103行目のQueryCloseイベントプロシージャが実行されます。
フォーム終了後は、フォームから(セルを通して)標準モジュールに値を渡す必要がありますので、フォーム側としての仕事は、セルに戻り値を書き込む事になります。
104行目「Sheets("Sheet1").Range("A6").Value = CLng(Me.Label4.Caption)」では、決められたセル位置(今回はA6セル)に「フォーム上の数式の計算結果(Label4)の値」を書き込みます。

この書き込んだA6セルの値を、図9の83行目でメッセージとして表示しています。ですのでそのセル位置は、送る側(104行目)と受け取る側(図9の83行目)で合わせておく必要があります。

7.フォーム記述式

7-1.標準モジュール

4番目のボタンをクリックした時に呼び出されるのが、図11のButton4プロシージャです。
  1. '========== ⇩(15) ボタン4による呼び出し ============
  2. Sub Button4()
  3.  UserForm4.Label1.Caption = CLng(Val(Range("A1").Value))
  4.  UserForm4.Show vbModal
  5.  MsgBox UserForm4.Label4.Caption
  6. End Sub
図11


112行目「UserForm4.Label1.Caption = CLng(Val(Range("A1").Value))」では、A1セルの値をフォーム上の計算式の先頭(Label1)に、直接書き込んでいます。
フォームに直接書き込んで「値を渡す処理」が終わったあとで、113行目「UserForm4.Show vbModal」でUserForm4をモーダルで起動します。この行の実行後は、図12のコードが実行されます。

UserForm4が閉じられた後に114行目「MsgBox UserForm4.Label4.Caption」で、フォーム上の処理結果(今回はLabel4の計算結果の値)を呼び出してメッセージとして表示します。

7-2.フォームモジュール

図11の113行目によって起動されたフォーム(UserForm4)のモジュールに記述されているコードが図12です。
  1. '========== ⇩(16) フォーム表示時の初期設定 ============
  2. Private Sub UserForm_Activate()
  3.  Me.TextBox1.Value = ""
  4.  Me.Label4.Caption = CLng(Me.Label1.Caption) * CLng(Val(Me.TextBox1.Value))
  5. End Sub
  6. '========== ⇩(17) TextBoxの値を変更した時 ============
  7. Private Sub TextBox1_Change()
  8.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  9.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  10. End Sub
  11. '========== ⇩(18) フォームを終了する時 ============
  12. Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
  13.  Cancel = True
  14.  Me.Hide
  15. End Sub
図12


フォームが起動されると121行目のActivateイベントプロシージャが呼び出されます。
但し、113行目のフォーム起動の前に112行目で「フォームのLabel1に値が書き込まれ」ますので、動く順番としては「Initializeイベント」→「Label1への値書き込み」→「Activateイベント」の順になります。ですので、121行目のActivateイベントを実行する時点では、Label1には値が入っています。

先に123行目の説明をします。
123行目「Me.Label4.Caption = CLng(Me.Label1.Caption) * CLng(Val(Me.TextBox1.Value))」では、標準モジュール側から書き込まれた値を元に「フォーム上の数式」の計算をし、その結果をLabel4に書き込んでいます。

戻って122行目の説明です。
今回の方式は「フォームを閉じた後で、フォーム上の値を読み取る方法」のため、フォームはHideで閉じて(≒隠して)います。Unloadしてしまうと、戻すはずの値まで消えてしまうためです。
このHideでフォームを閉じる事で、計算結果(Label4)も残るのと併せて「TextBox1の値も残る」ことになります。そのため「再度フォーム(UserForm4)を起動」すると、1つ前のTextBox1の値が見えてしまうのです。
(Label1は標準モジュール側から書き換えられますし、またLabel4は123行目で再計算されますが、TextBox1を初期化するコードが無いため)
そこで122行目「Me.TextBox1.Value = ""」で、TextBox1を初期化(空欄)しています。

ユーザーがTextBox1に値を入力すると、127行目のChangeイベントが発生し、まず128行目「Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)」で入力した値を「半角文字列」に変換します。
129行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

フォームを終了時には、133行目のQueryCloseイベントプロシージャが実行されます。
フォーム終了後は、標準モジュール側から処理結果(今回はLabel4の値)を取得する必要がありますので、フォームはHideで閉じておく(≒隠す)必要があります。
ですので134行目「Cancel = True」でフォーム終了処理をキャンセルし、135行目「Me.Hide」でフォームを隠します。

Hideで閉じた後、図11の114行目でフォーム上の処理結果(Label4)を読み取り、メッセージとして表示します。

8.フォーム引数渡し式

8-1.標準モジュール

5番目のボタンをクリックした時に呼び出されるのが、図13のButton5プロシージャです。
  1. '========== ⇩(19) ボタン5による呼び出し ============
  2. Sub Button5()
  3.  Dim Val5 As Long   '←フォームからの戻り値
  4.  Val5 = UserForm5.UFstart(CLng(Val(Range("A1").Value)))
  5.  MsgBox Val5
  6. End Sub
図13


今回の仕様は、標準モジュール側から「フォーム内にある関数プロシージャを呼び出す」形です。その呼び出す関数に「引数」という形で値を渡し、関数内(=フォーム内)で処理した結果を「戻り値」という形で受け取るものです。

144行目「Val5 = UserForm5.UFstart(CLng(Val(Range("A1").Value)))」の右辺では、フォーム(UserForm5)内のUFstart関数プロシージャを呼び出しています。引数として「CLng(Val(Range("A1").Value))」という「A1セルの数値」を渡します。
UFstart関数の戻り値(=フォーム内で処理した結果)は、左辺の変数Val5で受け取ります。

145行目「MsgBox Val5」では、受け取った戻り値をメッセージとして表示しています。

なお、図14の151行目でのデータ型指定を見て分かるように、UFstart関数の引数及び戻り値はLong型としていますので、引数はCLng関数を使い、また戻り値であるVal5は142行目「Dim Val5 As Long」でLong型指定にしています。

8-2.フォームモジュール

図13の144行目の右辺から呼び出されるのが、図14のフォーム(UserForm4)モジュールです。
151行目のUFstart関数プロシージャは、引数(Val5)として値(今回は、掛け算の元データ)を受け取ります。
  1. '========== ⇩(20) フォーム表示時の初期設定 ============
  2. Public Function UFstart(Val5 As Long) As Long
  3.  Me.Label1.Caption = Val5
  4.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  5.  Me.Show vbModal
  6.  UFstart = CLng(Me.Label4.Caption)
  7. End Function
  8. '========== ⇩(21) フォーム表示時の初期設定 ============
  9. Private Sub TextBox1_Change()
  10.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  11.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  12. End Sub
図14


標準モジュール側からUFstart関数プロシージャを呼び出すと、まずフォームが生成され、「Initializeイベント」が発生した後で「UFstart関数プロシージャ」が呼び出されます。関数プロシージャを呼び出しただけではフォームは起動しませんので、関数プロシージャ内で「Showメソッド」を使ってフォームを起動させる必要があります。

152行目「Me.Label1.Caption = Val5」では、引数で受け取った値(Val5)をフォーム上の計算式の先頭(Label1)に書き込みます。
153行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

154行目「Me.Show vbModal」では、自分(UserForm5)をモーダルで起動します。フォームを起動させると、次には「Activateイベント」が発生する、という順序になります。しかし今回は152~153行目で既に計算式のセットは完了していますので、Activateイベントプロシージャは不要です。
寄り道
異なる方法として図15のように、Activate内で152~153行目の処理を行う方法でもOKです。しかしUFstart関数が受け取った引数(Val5)をActivateイベントプロシージャへ引き渡してやる必要が出てくるので、「フォームレベル変数の設置」などの手間が増えます。
  1. '========== ⇩(22) フォームレベル変数の宣言 ============
  2. Dim Val6 As Long   '←フォームレベル変数
  3. '========== ⇩(23) フォーム内関数 ============
  4. Public Function UFstart(Val5 As Long) As Long
  5. ' Me.Label1.Caption = Val5
  6. ' Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  7.  Val6 = Val5   '←関数で受け取った引数をフォームレベル変数にする
  8.  Me.Show vbModal
  9.  UFstart = CLng(Me.Label4.Caption)
  10. End Function
  11. '========== ⇩(24) フォーム表示時の初期設定 ============
  12. Private Sub UserForm_Activate()
  13.  Me.Label1.Caption = Val6   '←フォームレベル変数値をLabel1に書き込む
  14.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  15. End Sub
  16. '========== ⇩(25) TextBoxの値を変更した時(全く同じ) ============
  17. Private Sub TextBox1_Change()
  18.  Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)
  19.  Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))
  20. End Sub
図15


フォーム上での処理が完了し、ユーザーがダイアログ右上×印をクリックするとフォームが閉じますので、コードは154行目→155行目「UFstart = CLng(Me.Label4.Caption)」に移ります。155行目では、計算結果であるLabel4の値を「UFstart関数プロシージャの戻り値」に設定し、終了します。

フォーム終了後は図13の145行目で、戻り値を使ってメッセージ表示をします。

話が前後する形になりますが、ユーザーがTextBox1に値を入力すると、159行目のChangeイベントが発生し、まず160行目「Me.TextBox1.Value = StrConv(Me.TextBox1.Value, vbNarrow)」で入力した値を「半角文字列」に変換します。
161行目「Me.Label4.Caption = Me.Label1.Caption * CLng(Val(Me.TextBox1.Value))」では、フォーム上の計算式を計算し、計算結果をLabel4に書き込みます。

フォーム終了時のQueryCloseイベントを使った処理は、この仕様では不要です。155行目での「関数の戻り値設定」がその役目を果たしている形になります。

9.まとめ

以上の仕様をまとめると図16のようになるかと思います。「適する場面」は、まだ私見の域です。
共通変数渡しフォーム変数渡しセル渡しフォーム記述フォーム引数渡し
受け渡しの仲介プロジェクトのPublic変数フォームのPublic変数セル-(直接書込み)関数の引数・戻り値
フォームの閉じ方Hide/UnloadHideのみHide/UnloadHideのみHide/Unload
メリット変数をどこからでも使える変数のスコープが狭い値の保存が可能フォーム内でコードを完結できる標準モジュール側のコードが単純
デメリット変数のスコープが広く危険フォームが生成されている間しか使えない行挿入等でセル位置が動く可能性有り隠しコントロールが必要な場合有り引数をフォームレベル変数に置き換えなければならない場合が多い
適する場面共通変数をプロジェクト内で活用する場合渡された値をフォーム内で活用する場合セル値の置換。設定条件保存として使う場合フォーム構造が複雑な場合関数プロシージャを可変型にしたい場合
図16


どの方法がベストという事は無いと思いますし、「適する場面」が的を得ているかも自信がありません。ただ、他のサイトで良く紹介されているのは「共通変数式」「セル渡し式」「フォーム引数式」なので、決められない場合はこの辺で進めるのが良いかと思います。
なお、少なくとも同じアプリ内では同じ仕様で統一した方が分かり易いとは思います。

アプリ実例

セルへの日付入力をカレンダー日付クリックで選定する
西暦・和暦対照表
年月をスクロールバーで選択する予定表ひな型
CSVファイルでデータを読み書きする月間予定表
グラフのX軸をスクロールバーで移動
複数行1データの並び替え
備品の予約・貸出・記録ができる貸出管理表
Excelで作った日報をPDFでそのままメール送信
先行予約可能な備品予約・貸出システム
図形カレンダーをクリックし日付入力



サンプルファイル

ユーザーフォームへの値の渡し方・戻し方(its-023.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。