【C#】同じフォームの多重表示を禁止する方法【Form】

とあるフォームに設置してあるボタンを押したら新しいフォームが開く、というような関係性を持つForm Applicationがあった時、フォーム多重表示を不可にする方法を考えてみました。

今回は、MainFormに設置してあるボタンを押したらSubForm1~3が開けるというケースで考えてみます。

同じフォームの多重表示を禁止するには?

普通にフォームのインスタンスを作成してShowメソッドで開くのがよくある書き方ですが、これだと各フォームの多重起動はできてしまいます。。。

private void Button1_Click(object sender, EventArgs e)
{
    Form subForm1 = new SubForm1();
    subForm1.Show();
}

private void Button2_Click(object sender, EventArgs e)
{
    Form subForm2 = new SubForm2();
    subForm2.Show();
}

private void Button3_Click(object sender, EventArgs e)
{
    Form subForm3 = new SubForm3();
    subForm3.Show();
}

多重表示を不可にするには

そこで、開かれているフォームをMainFormで管理し、二回目の表示のときに処理を中断すれば良いわけです。要は、MainFormにList<Form>型のプロパティをもたせておき、表示されたフォームをリストに追加し、既にそのフォームが追加されていたらreturnで処理を中断する・・ってのを実現したいわけです。

フォームによってクラスが違いますから、すべてのフォームクラスに対応できるようにジェネリックを使います。また、各子フォームが閉じられた時のイベントをMainForm側でキャッチしたいわけですから、デリゲートも使うことになります。

コーディングするとこんな感じになりました。

まず、基底フォームを作成し、このようにデリゲートを追加します。そしてこれを子フォームで継承。これは、後ほど出てくるMainFormのList<FormList> FormListから、閉じられるフォームのインスタンスを削除するためです。

public partial class FormBase : Form
{
    public delegate void FormCloseDelegate(FormBase form);
    public FormCloseDelegate FormClose;

    public FormBase()
    {
        InitializeComponent();
    }

    private void FormBase_FormClosing(object sender, FormClosingEventArgs e)
    {
        FormClose(this);
    }
}

SubForm1~3ではFormBaseを継承します・・

public partial class SubForm1 : FormBase
{
    public SubForm1()
    {
        InitializeComponent();
    }
}

最後にMainFormです。

子フォームの表示にはジェネリックを用いた専用の関数を使い、MainFormがプロパティとして所持するFormListに子フォームのインスタンスを突っ込みます。

public partial class MainForm : Form
{
    private List<FormBase> FormList = new List<FormBase>();

    public MainForm()
    {
        InitializeComponent();
    }

    private void Button1_Click(object sender, EventArgs e)
    {
        OpenNewForm<SubForm1>();
    }

    private void Button2_Click(object sender, EventArgs e)
    {
        OpenNewForm<SubForm2>();
    }

    private void Button3_Click(object sender, EventArgs e)
    {
        OpenNewForm<SubForm3>();
    }

    private void OpenNewForm<T>() where T : FormBase, new()
    {
        var form = this.FormList.Where(x => x.GetType() == typeof(T)).FirstOrDefault();

        // 既に開かれている場合
        if (form != null)
        {
            // 対象フォームを全面に表示
            form.Activate();
            return;
        }


        // インスタンス作成
        var newForm = new T();

        // 子フォームが閉じられる時に実行される処理
        newForm.FormClose += (x) =>
        {
            FormList.Remove(x);
        };

        // リストに追加
        this.FormList.Add(newForm);

        // フォームを表示
        newForm.Show();
    }
}

このコーディングでは必然的に1つのFormクラスにつき1つの表示しかできなくなりますが、デリゲートをうまく使うことで子フォームのイベントを起点に親フォームに対して複雑な処理をかけることができるようになります。

Ownerを使っても同じことができるけど・・

こちらのページでも記載していますが、ShowメソッドにFormオブジェクトを指定するとそのフォームがオーナーとして設定されます。

これを使って同じ動きを実現させることもできますが、、

public partial class FormBase : Form
{
    public FormBase()
    {
        InitializeComponent();
    }

    private void FormBase_FormClosing(object sender, FormClosingEventArgs e)
    {
        ((MainForm)this.Owner).FormList.Remove(this);
    }
}
public List<FormBase> FormList = new List<FormBase>();

~~~省略~~~

private void OpenNewForm<T>() where T : FormBase, new()
{
    var form = this.FormList.Where(x => x.GetType() == typeof(T)).FirstOrDefault();

    // 既に開かれている場合
    if (form != null)
    {
        // 対象フォームを全面に表示
        form.Activate();
        return;
    }

    // インスタンス作成
    var newForm = new T();

    // リストに追加
    this.FormList.Add(newForm);

    // フォームを表示
    newForm.Show(this);
}

MainFormのFormListをpublicにしなきゃいけないし、Formクラスの継承関係もぐちゃぐちゃになりそう(子フォーム用の基底クラス作る必要性が浮き出てくる)だし、カプセル化の観点から見たらどうなのって思った次第です。オブジェクト指向って難しい。

コメント

タイトルとURLをコピーしました