2009年5月23日土曜日

C# 複数フォームから1つのフォームを共有する

C#は.Net Frameworkを含めて生産性の高いツールだと感じます。
ある機能を持った親フォームから子フォームを呼び出して、子フォームのデータを親フォームに返したいことがよくあります。このような場合以下のように実装します。

public partial Class ParentForm : Form
{
    protected string dataFromCoForm;
    private void Button_Click(object sender, EventArgs e)
    {
        ChildForm childForm = new ChildForm(this);
        childForm.Show();
    }
}

public partial Class ChildForm : Form
{
    ParentForm parentForm;
    public ChildForm(ParentForm o)
    {
        parentForm = o;
    }
    public ChildForm_Load()
    {
        parentForm.dataFromCoForm = “親フォームへの文字列”;
    }
}

このコードでは親フォームが子フォームを呼び出す際に自分の参照を渡しています。
子フォームでは受け取った親フォームの参照を親フォーム型のフィールドにコピーしています。
親フォームには子フォームからアクセスできるフィールをがあるため参照を通して親フォームにデータを返すことができます。

ここで他のフォームを作成して同じ子フォームを呼び出そうとすると問題が生じます。
新しく作成したフォームをnewParentFormクラスとした場合、newParentForm型の参照が子フォームに渡されます。子フォームは渡された参照をparentForm型のフィールドへコピーしようとしますが、これは失敗します。(実際にはコンパイル時点でエラーになります)単純に型が違うことが原因です。

子フォームがまったく同じでよいのなら共有するほうが生産性もあがるし、たった1行のために似たような子フォームクラスを作らずに済みます。以下に、このような場合の解決方法を示します。

まず基本的な考え方を説明します。
子フォームを共有する親フォームは、派生元になる中間フォームを作成します。通常親フォームはWindows.Formsクラスから派生しますが、Formsクラスから中間フォームを派生させて、さらに親フォームを派生するように作成します。
中間フォームには子フォームから値を返すフィールドやプロパティ、あるいはメソッドを準備します。
子フォームは親フォームから受け取った参照を中間フォーム型のフィールドにコピーします。親フォームは中間フォームから派生しているためコピーは成功します。
では、以下にコードを示します。

public partial class 中間フォーム : Form
{
    public 中間フォーム()
    {
        InitializeComponent();
    }
    public virtual void SetValue()
    {
    }
}

public partial class 親フォーム1 : 中間フォーム
{
    public 親フォーム1()
    {
        InitializeComponent();
    }
    private void btnCallChildForm_Click(object sender, EventArgs e)
    {
        子フォーム CoForm = new 子フォーム(this);
        CoForm.Show();
    }
    public override void SetValue(string s)
    {
        MessageBox.Show("親フォーム1");
        this.textBox.Text = s;
    }
}

public partial class 親フォーム2 : 中間フォーム
{
    public 親フォーム2()
    {
        InitializeComponent();
    }
    private void btnCallChildForm_Click(object sender, EventArgs e)
    {
        子フォーム CoForm = new 子フォーム(this);
        CoForm.Show();
    }
    public override void SetValue(string s)
    {
        MessageBox.show("親フォーム2");
        this.textBox.Text = s;
    }
}

public partial class 子フォーム : Form
{
    object Obj;
    public 子フォーム()
    {
        InitializeComponent();
    }
    public 子フォーム(object o)
    {
        InitializeComponent();
        Obj = o;
    }
    private void btnReturn_Click(object sender, EventArgs e)
    {
        中間フォーム superForm = (中間フォーム)Obj;
        superForm.SetValue("この値は子フォームからの値です。");
        this.Close();
    }
}

コードは以上です。
2つのフォームから共通のフォームを共有することができています。
中間フォームにはテキストボックスに値をセットするためのメソッドが準備してあります。このメソッドはvirtualメソッドになっていて、2つの親フォームでoverrideしています。中間フォームのメソッドを仮想メソッドにすることで呼び出し元の親フォームのメソッドが実行されるようになります。

この方法で1つのフォームを共有できますが注意点があります。
子フォームはsetValueメソッドで親フォームへデータを返すため、新しく作成する親フォームはsetValueメソッドをオーバーライドする必要があります。
また、今回メソッドで値を親フォームにセットしていますが、これはTextBoxへ値をセットするための手法です。もし、フィールドにデータをセットした場合、親フォームでTextBoxに値をコピーしなくてはならないためメソッドで値をセットしました。

ある程度規模の大きいソフトを作ろうと考えているなら、始から中間フォームを準備しておいたほうがよいかもしれません。

0 件のコメント:

コメントを投稿