みなさんこんにちは!ひろぽんです!
今回はFormから別のFormに値を持って行ったり、今開いているFormから値を受け取ったりする為の実装についてみていきたいと思います!
[s_ad]

パターン1:MainFormから別Formに値を渡す
💡 「Formの中にFormを表示するMDI的な切り替え」をやりたいときはC# Formの中にFormを表示して良い感じに切り替えるでまとめてます。値受け渡しの応用編としても使えますよ。
まずはこんな感じのFormを2つ用意します。


でそれぞれ中のコードをこんな感じにします。
namespace BringValueWithForm
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var form = new SubForm(textBox1.Text);
form.ShowDialog();
}
}
}
namespace BringValueWithForm.Pattern1
{
public partial class SubForm : Form
{
private string _str;
public SubForm(string str)
{
InitializeComponent();
_str = str;
}
private void SubForm_Load(object sender, EventArgs e)
{
label1.Text = $@"MainFormから{_str}の値が来ました。";
}
private void button1_Click(object sender, EventArgs e)
{
}
}
}
これを実行するとこんな感じ!


ちゃんと値がわたっている!!
パターン2:MainFormで別Formから値をもらう
で次はパターン2。
今回はMainFormから開いたSubFormで何かしらの入力がされて、その値をMainFormで受け取るというもの。


こんな感じでパターン1とは反対のFormを用意する。
中身はこんな感じ。
namespace BringValueWithForm.Pattern2
{
public partial class MainForm2 : Form
{
public MainForm2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var form = new SubForm2();
form.ShowDialog();
//ここでform1が閉じられたら
label1.Text = $@"SubFormから{form.TextBox1Str}の値が渡ってきました。";
}
}
}
namespace BringValueWithForm.Pattern2
{
public partial class SubForm2 : Form
{
public string TextBox1Str { get; private set; } = "";
public SubForm2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
TextBox1Str = textBox1.Text;
this.Close();
}
}
}
これを実行するとこんな感じ

立ち上げ時はただのlabelだが。

SubFormでtestと入力されてからSubFormが閉じられると。

こんな感じで値が渡った!
[s_ad]
GitHubでソースを公開しています!
下記リポジトリのBringValueWithFormというフォルダで、今回のソースを公開しています!
リポジトリの詳細は下記を参照ください!

💡補足:業務系の現場でよくハマるパターン
私もこのForm間値受け渡しを実装で何度もやってますが、コードレビューで指摘されるパターンが3つあるんですよね。書いておくと事故が減ります。
① Form2のコンストラクタ引数を後から追加すると、呼び出し元が全部エラー
「とりあえず引数で渡せばいいか」でpublic Form2(string name)を作ったあと、後から「やっぱり日付も渡したい」となってpublic Form2(string name, DateTime date)に変えると、Form2を呼んでる箇所が全部コンパイルエラーになります。引数追加のたびに修正点が増えるんですよね。回避するなら最初からデータクラスを1つ作って渡す形にしておくと楽です。C#のコールバックとデリゲートの違いで書いたデリゲート渡しも、似た思想で使い分けできます。
② Form2で入力した値をForm1のグリッドに即反映する時のタイミングミス
Form2をShowDialog()で開いて値を受け取った後、Form1のDataGridViewを即更新したいケース。これ、ShowDialog()の戻り値でOK/Cancelを判定してから更新しないと、ユーザーがキャンセルしたのに更新が走るバグになります。Form.ShowDialogとForm.Showの違いと使い分けで深掘りしてますが、業務画面はほぼShowDialog一択です。
③ publicフィールドで値共有したら他チームに触られてバグ拡散
「Form2.Result」みたいなpublicフィールドで結果を渡すパターン、書くのは楽ですが、別チームが「ここから取れるんだ」と覚えてしまい、想定外の場所から参照される事故が起きます。プロパティ+ private setterにしておくか、戻り値で渡すか、いずれにせよカプセル化は最初からやっとくと後で泣かない。
❓よくある質問
Q1. Form1とForm2で同じデータを共有したい時、毎回引数で渡すのが面倒です
A.共有するデータが3項目以上あるなら、専用のデータクラス(DTO)を1つ作って、その参照を渡してインスタンスを共有する(いわゆる共有渡し)のが定石です。引数1個で済むのでForm2のコンストラクタも増えにくくて、結果的に保守が楽になります。データクラスをDataGridViewのDataSourceとしても流用できる作りにしておくと、現場の幅が広がります。
Q2. Form2で入力した値をForm1のグリッドに即反映するには?
A. Form2をShowDialog()で開き、戻り値のDialogResultがOKの時だけForm1側のメソッド(例: RefreshGrid())を呼ぶ作りにします。Form2側でthis.DialogResult = DialogResult.OK; this.Close();を発行する型ですね。詳細はForm.ShowDialog vs Form.Showの方に書いてます。
Q3. Formの値受け渡しでpublic静的変数を使うのって良くないですか?
A.動きはしますが、業務系の現場では避けるのが無難です。理由は2つあって、(1)どの画面から参照されてるか追えなくなる、(2)スレッド絡みで競合する。代わりに「DTOを引数で渡す」「イベント/デリゲートで通知する」のどちらかで設計しておくと、後輩が触っても事故りにくいです。
Q4. ShowDialogとShowで値の受け渡し方が違うって本当ですか?
A.本当です。ShowDialogは「閉じるまで処理がブロックされる」ので、戻り値や閉じた直後にプロパティ参照で値を取れます。Showは「即返ってきて並行動作」なので、イベントやデリゲート経由で通知する形が必要。使い分けガイドでコード付きでまとめてます。
Q5. DBの値をForm間で持ち回りたい時のパターンは?
A. DBアクセス層を共通クラスに切り出して、各Formがそこを叩く形にするのが王道です。Form間で「DataTableをそのまま渡す」もありですが、メモリ消費が読めなくなるので件数が多いなら避けたい。DataAdapterでFillとUpdateしてみるで書いたDataAdapterの使い分けが効きます。
📚関連記事
- 【C#】Formの中にFormを表示して良い感じに切り替える — MDI的な画面切り替えをやりたい時の定番パターン
- 【C#】Formから別フォームを表示しメインフォームを切り替える —親子関係を切り替えながら値を持ち回るパターン
- C# WinFormsのForm.ShowDialogとForm.Showの違いと使い分け —値受け渡しの設計はここで決まる
- 【C#】DataAdapterを使ってFillとUpdateしてみる — FormとDBのデータ往復をやる時に押さえとくやつ
- C#のコールバックとデリゲートの違いはなんなのか — Form間の通知をデリゲート渡しでやる時の前提知識


コメント