孫フォーム呼び出し時のチェックボックスの値

タグの編集
投稿者 伊右衛門  (社会人) 投稿日時 2021/1/15 16:55:01
お世話になっております。また質問よろしくお願いします。

親フォーム Form1(Button1)
子フォーム Form2(Button1,CheckBox1,CheckBox2)
孫フォーム Form3(Button1)

親フォームのButtonにて子フォームを呼び出し
Dim f2 As New Form2
f2.ShowDialog(Me)
f2.Dispose()

次に、子フォームのCheckBox1かCheckBox2のどちらかを選択し、Buttonで孫フォームを呼び出し
Dim f3 As New Form3
f3.ShowDialog(Me)
f3.Dispose()

孫フォームにて
If Form2.CheckBox1.Checked = True Then
   CheckBox1に応じた変数設定
Else
   CheckBox2に応じた変数設定
End If

というように書いているのですが、、、
例えば、子フォームにてチェックボックス1を選択して孫フォームを呼び出しても
孫フォームにて設定された変数の中身が「CheckBox2に応じた変数設定」となってしまいます。

親-子間ではできるのですが、子-孫間ではできないのはなぜなのでしょうか?
よろしくお願いします。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/1/15 18:44:35
「Dim f2 As New Form2()」と「Form2」は別のインスタンスですし、
「Dim f3 As New Form2()」と「Form3」も別のインスタンスです。

Dim x As New Form2()
Dim y As New Form2()
x.Show(Me)
y.Show(Me)
を実行してみてください。Form2 が 2 つ開きますよね。
New することで、新たなフォーム インスタンスをいくつでも生成できるわけです。

ですから、Form3 で
 If Form2.CheckBox1.Checked = True Then
と書いても、これは x や y とは、まったく別の Form2 であることを意味します。
恐らく、この場合の Form2 上のコントロールの状態は、初期設定のままになっているはずです。

もし、Form2 のデザイン時に 、CheckBox1.Checked が False の状態であったのなら、
この If 文は満たさず、Else 句が実行されることになります。


> 親-子間ではできるのですが、子-孫間ではできないのはなぜなのでしょうか?
その「親」というのは、スタートアップ フォームに設定されているのではありませんか?
スタートアップ フォームは 自分で New して生成されたものでないため、
管理方法が異なるという事です。

New して生成されたフォームだとすれば、親子でも子孫でも同じ結果になりますし、
スタートアップフォームであっても、「Dim f1 As New Form1()」などとすれば、
それは「Form1」とは別のインスタンスということになります。

つまり「Form2.CheckBox1.Checked」のような書き方は、
暗黙のフォーム変数と呼ばれる書き方であり、
New Form2() で管理する方法とは共存できない、ということです。

また、New を使ってフォームを生成するかどうかによらず、
暗黙のフォーム変数は使わない方が望ましいです。


ではどうするかというと…方法はいくつかあります。


(方法1) Owner プロパティを使う

> f3.ShowDialog(Me)
で渡している Me こそが、本当に操作したい Form2 のインスタンスを表しているわけですから、
Form3 側で Me.Owner を取得すれば、ここから Form2 を得る事が可能というわけです。

ただし、Owner プロパティの型は As Form2 ではなく As Form ですから、
そこから CheckBox1 にアクセスするには、Form2 にキャストするか、
Me.Owner.Controls 経由で CheckBox1 にアクセスする必要があります。

この方法は、呼び出し元のフォームが Form2 である、ということを Form3 に
強制することになるため、他フォームから呼び出せるように改修したり、
画面遷移順の変更などに脆くなるため、保守性という点から、あまりお奨めしません。
フォーム関係の依存性が強くなりすぎます。

メッセージボックスに TextBox の内容を渡して表示させることはあっても、
メッセージボックス自体が TextBox の内容を読み取って表示することは
普通行われませんよね。

それと同じで、子が親を直接参照する設計は避けるべきです。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/1/15 18:56:06
(方法2) CheckBox の値を Form3 に最初から渡しておく

ShowDialog を呼び出しているという事は、Form3 を呼び出している間、
Form2 はユーザー操作を受け付けません。ということは、Form3 を閉じるまで、
Form2 の CheckBox1 の状態は変化しないわけですよね。

そこで、Form3 を呼び出す際に、最初から CheckBox1 の値を渡しておくのはどうでしょう。
たとえば、下記のように受け渡せるよう、Form3 のコンストラクタに引数を追加するようにします。

Dim f3 As New Form3(CheckBox1.Checked)

Public Class Form3
    Private ChildFormChecked As Boolean = False
    Public Sub New(checked As Boolean)
        ChildFormChecked = checked
    End Sub
End Class


上記ではコンストラクタで渡していますが、
プロパティで渡したり、メソッドの引数として渡す方法もあります。



(方法3) イベントによるコールバックを用いる

まずは説明のため、フォント選択画面を開くための FontDialog というクラスを紹介させてください。

このクラスでは、ShowApply = True に設定しておくと、Apply イベントという物が使えるようになります。
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.fontdialog.apply
Private Sub FontDialog1_Apply(sender As object, e As EventArgs) Handles FontDialog1.Apply
 TextBox1.Font = FontDialog1.Font
End Sub


この場合、ダイアログを閉じずとも、[適用]ボタンを押したタイミングで即座に、
呼び出し元の画面のフォント設定を変更することができることになります。

この方法だと、呼び出し元のフォームが、イベント応答によって、
自分自身のフォームを操作しているのだけなので、
> 子が親を直接参照する設計
を避ける事ができます。

案1 の手法とは異なり、親(Form)が子(Dialog)のプロパティ(Font)等を使って
親自身の画面を操作しているだけなので、画面間の依存性がありません。

これにより、どのフォームからでも呼び出し可能な、
再利用性の高い汎用的なダイアログ設計にすることができます。

イベントを通じてやり取りする方法は、下記の案4などでも紹介しています。
http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30578
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/1/15 18:57:17
誤字訂正:

> 「Dim f3 As New Form2()」と「Form3」も別のインスタンスです。

「Dim f3 As New Form3()」と「Form3」も別のインスタンスです。
投稿者 伊右衛門  (社会人) 投稿日時 2021/1/16 11:19:28
 魔界の仮面弁士様

丁寧なご説明ありがとうございました。
自分の思っていたことが全く見当違いだったということに気づかされました。
いただいたサンプルを元に問題点を修正することができました。

本当にありがとうございました。