とあるフォームに設置してあるボタンを押したら新しいフォームが開く、というような関係性を持つ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クラスの継承関係もぐちゃぐちゃになりそう(子フォーム用の基底クラス作る必要性が浮き出てくる)だし、カプセル化の観点から見たらどうなのって思った次第です。オブジェクト指向って難しい。
コメント