WindowsFormsアプリで2つ目のフォームを操作

複数のフォームを実行時に操作できる方法について説明します

ここでは、様々は方法を記述していますが全ての方法を同時に実装することを意味するものではありません
学習目的のためなので、選択肢を広げることが目的と考えてください

実際は、ソフトウェア開発の基本法則に従うことが大切です

ソフトウェア開発法則情報の一部

下記の他にデザインパターンを学ぶこともコーディングスキル向上に有効です
ここでは、割愛していますが、このブログの検索窓でも「デザインパターン」でヒットします

2つ目のフォームを作成する

Windows Formsアプリケーションで2番目のフォームを作成するには、以下の手順に従います。

  1. Visual Studioを開きます。
  2. プロジェクトを作成し、Form1が作成されたら、Form2を作成する必要があります。
  3. Form2を作成するには、[プロジェクト]メニューの[追加]をクリックし、[新しい項目]を選択します。
  4. [追加]ダイアログボックスが表示されたら、[Windows Form]を選択し、[名前]に「Form2」と入力し、[追加]をクリックします。
  5. Form2が作成され、デザインビューが表示されます。

この時点で、Form1とForm2が作成されていますが、Form2を表示するには、Form1からForm2に遷移する方法が必要です。

  1. Form1を開き、Form2を表示するためのボタンを追加します。たとえば、「Show Form2」ボタンを追加します。
  2. ボタンをダブルクリックし、ボタンのクリックイベントを作成します。
  3. クリックイベント内で、以下のコードを追加してForm2を表示します。
private void showForm2Button_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();
    form2.Show();
}

これで、Form1の「Show Form2」ボタンをクリックすると、Form2が表示されます。

注意点としては、Form2を閉じてもアプリケーションが終了しないように、FormClosingイベントを処理する必要があることです。また、Form2を閉じた後に再び表示する場合は、form2.Show()ではなく、form2.ShowDialog()を使用する必要があります。

フォーム1からフォーム2にアクセス

メソッド呼び出しを使う

Windows Formsアプリケーションで、フォーム1からフォーム2のメソッドを呼び出すには、以下の手順に従います。

  1. フォーム2に呼び出したいメソッドを定義します。たとえば、以下のようなメソッドを追加します。
public void MyMethod()
{
    // 何らかの処理
}
  1. フォーム1からフォーム2を呼び出す場合は、以下のようにします。
// フォーム2をインスタンス化します。
Form2 form2 = new Form2();

// フォーム2のMyMethodメソッドを呼び出します。
form2.MyMethod();

このようにすることで、フォーム1からフォーム2のMyMethodメソッドを呼び出すことができます。

ただし、この方法ではフォーム1とフォーム2が直接的に結合してしまうため、処理の流れが複雑になる可能性があります。そのため、アプリケーションの構造や設計に応じて、別の方法を検討することが重要です。例えば、イベントを使用してフォーム間で通信する方法や、共有クラスを使用してフォーム間でデータを共有する方法などがあります。

イベントを使う

フォーム1からフォーム2のメソッドをイベントを使って呼び出す場合の手順を以下に示します。

  1. Form2に、フォーム1から呼び出したいメソッドを実装します。たとえば、以下のようなメソッドを作成します。
public void DoSomethingInForm2()
{
    // 何らかの処理を実行する
}
  1. Form2に、フォーム1から呼び出すためのイベントを宣言します。たとえば、以下のようにEventHandlerを使用してイベントを宣言します。
public event EventHandler DoSomethingInForm2Requested;
  1. Form2のイベントハンドラで、イベントが発生したことをフォーム1に通知します。以下のように、イベントハンドラ内でDoSomethingInForm2Requestedを呼び出します。
private void someButton_Click(object sender, EventArgs e)
{
    // DoSomethingInForm2を呼び出す
    DoSomethingInForm2();

    // DoSomethingInForm2Requestedイベントを発生させる
    DoSomethingInForm2Requested?.Invoke(this, EventArgs.Empty);
}
  1. Form1で、Form2のイベントを受け取るハンドラを宣言し、Form2のインスタンスを生成します。
private void ShowForm2Button_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();

    // Form2のイベントハンドラを設定する
    form2.DoSomethingInForm2Requested += Form2_DoSomethingInForm2Requested;

    form2.Show();
}

private void Form2_DoSomethingInForm2Requested(object sender, EventArgs e)
{
    // Form2のイベントが発生したら、何らかの処理を実行する
    // たとえば、Form2のテキストボックスの内容を取得するなど
}

以上の手順で、Form1からForm2のメソッドをイベントを使って呼び出すことができます。イベントを使用することで、Form2の実装の詳細をForm1から隠すことができます。また、Form2で何らかの処理が完了したときにForm1に通知することができるため、アプリケーション全体の処理の流れをより明確にすることができます。

フォーム2からフォーム1にアクセス

以下のすべての例では、メソッドの呼び出しを想定していますが、その他メンバー(Publicフィールド、プロパティ、インデクサーなど)でも基本同様の方法で可能です

メソッド呼び出しを使う

フォーム2からフォーム1のメソッドを呼び出すには、フォーム1のインスタンスをフォーム2に渡し、そのインスタンスを使用してメソッドを呼び出します。以下は、C#言語を使用してフォーム2からフォーム1のメソッドを呼び出す例です。

  1. フォーム1のメソッドを定義します。たとえば、次のようなメソッドがあるとします。
public void SomeMethod()
{
    // メソッドの処理
}
  1. フォーム2のクラスに、フォーム1のインスタンスを格納するプロパティを定義します。たとえば、次のようなプロパティがあるとします。
private Form1 _form1;

public Form1 Form1Instance
{
    get { return _form1; }
    set { _form1 = value; }
}
  1. フォーム2を表示する前に、フォーム2のForm1Instanceプロパティにフォーム1のインスタンスを設定します。たとえば、次のようにします。
Form2 form2 = new Form2();
form2.Form1Instance = this; // thisはフォーム1のインスタンス
form2.Show();
  1. フォーム2の中で、フォーム1のメソッドを呼び出します。たとえば、次のようにします。
if (Form1Instance != null)
{
    Form1Instance.SomeMethod();
}

上記の例では、Form1Instanceプロパティがnullでない場合に、Form1Instance.SomeMethod()を呼び出しています。これにより、フォーム2がフォーム1のインスタンスを持っていない場合にプログラムがクラッシュすることを防ぎます。

なお、この方法は、フォーム1とフォーム2が同じスレッドで動作している場合に限ります。異なるスレッドで動作している場合には、別の手法を使用する必要があります。

イベントを使う

フォーム2からフォーム1のメソッドをイベントを使って呼び出すには、以下の手順に従ってください。
上記(フォーム1からフォーム2の手順)とは方法論が違いますが、1つの手法で学習のためと考えてください

  1. フォーム1のメソッドをイベントハンドラとして作成します。たとえば、以下のようなメソッドを作成します。
public void MyMethod()
{
    // メソッドの処理
}
  1. フォーム1にイベントを作成します。たとえば、以下のようにします。
public event EventHandler MyEvent;
  1. フォーム2からフォーム1のメソッドを呼び出すために、イベントを発生させます。たとえば、以下のようにします。
private void callMethodButton_Click(object sender, EventArgs e)
{
    if (MyEvent != null)
    {
        MyEvent(this, EventArgs.Empty);
    }
}
  1. フォーム2で、フォーム1のイベントを購読します。たとえば、以下のようにします。
private void Form2_Load(object sender, EventArgs e)
{
    Form1 form1 = (Form1)Application.OpenForms["Form1"];
    form1.MyEvent += Form1_MyEventHandler;
}

private void Form1_MyEventHandler(object sender, EventArgs e)
{
    Form1 form1 = (Form1)sender;
    form1.MyMethod();
}

このようにすると、フォーム2のボタンをクリックすると、フォーム1のMyMethod()メソッドが呼び出されます。注意点としては、フォーム1がまだ開かれていない場合は、Application.OpenFormsメソッドでフォーム1を取得することができないため、エラーが発生することがあります。そのため、フォーム1が開かれていることを確認する必要があります。また、イベントハンドラ内でフォーム1のコントロールを操作する場合は、Invokeメソッドを使用する必要があることにも注意してください。

フォーム1とフォーム2で利用できる共有クラスを使う

フォーム1からフォーム2のメソッドを共有クラスを使って呼び出すには、以下の手順に従います。

  1. 共有クラスを作成します。共有クラスには、Form1とForm2からアクセスできるように、共有したいメソッドを作成します。
public class SharedClass
{
    public static void SharedMethod()
    {
        // 共有したい処理を記述
    }
}
  1. Form1でSharedClass.SharedMethod()を呼び出すことで、共有クラスのメソッドを実行できます。
private void callSharedMethodButton_Click(object sender, EventArgs e)
{
    SharedClass.SharedMethod();
}
  1. Form2でも同様に、SharedClass.SharedMethod()を呼び出すことで、共有クラスのメソッドを実行できます。
private void callSharedMethodButton_Click(object sender, EventArgs e)
{
    SharedClass.SharedMethod();
}

これにより、Form1とForm2で同じ処理を共有することができます。また、SharedClassには、複数の共有メソッドを追加することもできます。

ただし、共有クラスを使用する際には、複数のスレッドで同時に呼び出される可能性があるため、スレッドセーフにする必要があります。適切な同期を実装するなど、スレッドセーフな方法で実装することが重要です。

スレッドセーフにするには

スレッドセーフな共有クラスを実装するには、以下のような方法があります。

  1. ロックを使用する

複数のスレッドが同時にアクセスする可能性がある共有メソッドには、ロックをかけて同期します。ロックをかけることで、複数のスレッドが同時にメソッドにアクセスすることを防止することができます。

public class SharedClass
{
    private static readonly object lockObject = new object();

    public static void SharedMethod()
    {
        lock (lockObject)
        {
            // スレッドセーフにするために、ロックをかけた処理を記述
        }
    }
}
  1. スレッドセーフなコレクションを使用する

コレクションを使用する場合、複数のスレッドが同時にアクセスする可能性があるため、スレッドセーフなコレクションを使用する必要があります。

public class SharedClass
{
    private static readonly ConcurrentDictionary<string, string> dictionary = new ConcurrentDictionary<string, string>();

    public static void AddValue(string key, string value)
    {
        dictionary.TryAdd(key, value);
    }

    public static bool RemoveValue(string key)
    {
        return dictionary.TryRemove(key, out _);
    }
}
  1. スレッドセーフなメソッドを使用する

.NET Frameworkには、スレッドセーフなメソッドが用意されています。これらのメソッドを使用することで、スレッドセーフな処理を簡単に実装することができます。

public class SharedClass
{
    private static readonly Lazy<int> value = new Lazy<int>(() =>
    {
        return ComputeValue();
    }, LazyThreadSafetyMode.ExecutionAndPublication);

    public static int GetValue()
    {
        return value.Value;
    }

    private static int ComputeValue()
    {
        // スレッドセーフに計算する処理を記述
    }
}

これらの方法を使用して、スレッドセーフな共有クラスを実装することができます。ただし、スレッドセーフな実装を行う場合でも、適切な性能を維持するために、余分な同期を避けるように注意する必要があります。

共有クラスを静的クラスではなく、シングルトンで作成

共有クラスをシングルトンに変更することで、アプリケーション全体で唯一のインスタンスを持つことができます。以下は、C#でシングルトンを実装する例です。

public class SharedClass
{
    private static readonly Lazy<SharedClass> instance = new Lazy<SharedClass>(() => new SharedClass());

    private SharedClass()
    {
        // コンストラクタを private にすることで、外部からのインスタンス化を防止
    }

    public static SharedClass Instance
    {
        get
        {
            return instance.Value;
        }
    }

    public void SharedMethod()
    {
        // 共有したい処理を記述
    }
}

このように、シングルトンにすることで、外部からのインスタンス化を防止し、共有メソッドを呼び出す場合は 次のようにします

SharedClass.Instance.SharedMethod();

また、必要に応じて、スレッドセーフな実装を追加することもできます。

おまけ

2つ目のフォーム作成時に、コンストラクタの引数にに1つ目のフォームのインスタンス代入します

以下のコードは、WindowsFormsアプリで2つ目のフォームを作成し、1つ目のフォームのインスタンスを引数に渡す方法を示しています。

// 2つ目のフォーム
public class SecondForm : Form
{
    private Form firstForm;

    public SecondForm(Form form)
    {
        // 1つ目のフォームのインスタンスを保存
        this.firstForm = form;

        // フォームのプロパティを設定
        this.Text = "2つ目のフォーム";
        this.Size = new Size(300, 200);

        // ラベルを追加
        Label label = new Label();
        label.Text = "1つ目のフォームのインスタンスを受け取りました";
        label.Location = new Point(10, 10);
        label.AutoSize = true;
        this.Controls.Add(label);
    }
}

このコードでは、SecondFormというクラスを作成し、コンストラクタにForm型の引数formを受け取ります。これにより、SecondFormのインスタンスを作成するときに、1つ目のフォームのインスタンスを渡すことができます。

次に、受け取った1つ目のフォームのインスタンスをfirstFormというプライベートフィールドに保存します。そして、フォームのプロパティを設定し、ラベルを追加しています。

以上のように、SecondFormのコンストラクタに1つ目のフォームのインスタンスを渡して、2つ目のフォームを作成することができます。