none
フォーム間でのコントロールのコピーについて RRS feed

  • 質問

  • 現在、二つのフォームでコントロールをコピーすると言うことをやっているのですが、どうしてもMoveになってしまいます(オブジェクトである以上は参照渡しになってしまってるのでしょうか)
    フォーム1から、フォーム2にコントロールをコピーした後も、フォーム1上にコントロールを残す方法はないでしょうか

    以下のサンプルで試しています
    • フォーム1上にはパネルがいくつかあり、その上に部品が色々乗っています
    • フォーム2上は何もありません
    • ボタン2をクリックすると、フォーム1からフォーム2にコントロールが転送され、
    • ボタン3をクリックすると、フォーム2からフォーム1にコントロールを取り戻します

    フォーム1
            Form2 f = new Form2();
            public Control[] GetAllControls(Control top) {
                ArrayList buf = new ArrayList();
                foreach (Control c in top.Controls) {
                    if (c is Panel) {
                        buf.Add(c);
                        buf.AddRange(GetAllControls(c));
                    }
                }
                return (Control[])buf.ToArray(typeof(Control));
            }

            private void button2_Click(object sender, EventArgs e) {
                f.Show();
                Control[] all = GetAllControls(this);

                f.C_a = all;
                f.SetControl2();
            }

            private void button1_Click(object sender, EventArgs e) {

            }

            private void button3_Click(object sender, EventArgs e) {
                Control[] a = f.C_a;
                foreach (Control c in a) {
                    Controls.Add(c);
                }
            }


    フォーム2
            private Control[] g_c = new Control[100];

            public Control[] C_a {
                set { g_c = value; }
                get { return g_c; }
            }

            public void SetControl(Control c) {
                Controls.Add(c);
            }

            public void SetControl2() {
                foreach (Control c in g_c) {
                    Controls.Add(c);
                }
            }
    2010年1月13日 1:53

回答

  • ウィンドウの親は常に一つでなければなりません。Windows.Forms ではボタンやテキストボックスもウィンドウです。
    ご呈示されているコードでは「親変更」になるのです。

    そういうことをしたいなら、単にフォーム1のインスタンスをもう一つ作ればよいのではないですか?

    2010年1月13日 3:04
  • 現在、二つのフォームでコントロールをコピーすると言うことをやっているのですが、どうしてもMoveになってしまいます(オブジェクトである以上は参照渡しになってしまってるのでしょうか)
    その通りで、参照渡しになっています。ディープコピーする必要があります。一番簡単なのはシリアライズして、そこからデシリアライズする方法ですが、コントロールはシリアライズ不可ですので、そのままではこの方法を取ることができません。これを可能にするにはISerializationSurrogateインターフェースを利用したりしますが、敷居は高いと思います。

    (参考)
    MSDN Magazine Archivesは宝の山
    http://d.hatena.ne.jp/Kazzz/20051118/p1


    シャローコピーを実現するMemberwiseCloneメソッドを使用し、必要な参照型のみを自分でコピーする実装を書くことによって、部分的にディープコピーを実現しても良いかもしれません。

    (参考)
    シャローコピー と ディープコピー
    http://blogs.wankuma.com/jeanne/archive/2006/04/07/22287.aspx

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2010年1月13日 5:27
    モデレータ
  • 現在、二つのフォームでコントロールをコピーすると言うことをやっているのですが、どうしてもMoveになってしまいます(オブジェクトである以上は参照渡しになってしまってるのでしょうか)
    その通りで、参照渡しになっています。ディープコピーする必要があります。一番簡単なのはシリアライズして、そこからデシリアライズする方法ですが、コントロールはシリアライズ不可ですので、そのままではこの方法を取ることができません。これを可能にするにはISerializationSurrogateインターフェースを利用したりしますが、敷居は高いと思います。
    コントロールのウィンドウハンドルを別の値にしないといけないはずなので、仮にディープコピーやシャローコピーで値のコピーを行ったとしても、「ハンドルの値が同じ」であるかぎり、コントロールは移動されるはずです。

    Control を Add するという処理は、若干ややこしい事になってます。

    2010年1月13日 6:57
  • コントロールのウィンドウハンドルを別の値にしないといけないはずなので、仮にディープコピーやシャローコピーで値のコピーを行ったとしても、「ハンドルの値が同じ」であるかぎり、コントロールは移動されるはずです。

    Control を Add するという処理は、若干ややこしい事になってます。

    なるほど。そういう事情があるのですね。ぼんやりとした感想ですが、コントロールがシリアライズ不可になっているのは、この辺りの仕組みにも関係しているかもしれませんね。
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2010年1月13日 7:47
    モデレータ
  • 「コピーをしたい」ということなのに、何故「移動」になってしまうか。それは、「コピーを作っていないから」です。

    
    private void button2_Click(object sender, EventArgs e) {
        f.Show();
        // ここでコントロールを取り出し、
        Control[] all = GetAllControls(this);
        // ここで“参照させて”いる
        f.C_a = all;
        f.SetControl2();
    }
    
    // こちらは「参照の値渡し」
    // もし、「値の値渡し」をやったなら、
    // Form1 に同じコントロールが2つ出来ることになる
    public void SetControl(Control c) {
        Controls.Add(c);
    }
    
    public void SetControl2() {
    	// ここで親のすげ替えをやっているので「移動」になる
    	// 囚人さんが提示されている、囚人さんのブログ参照
        foreach (Control c in g_c) {
            Controls.Add(c);
        }
    }
    

    コピーするなら、こんな感じ。

    
    private void button2_Click(object sender, EventArgs e) {
        f.Show();
        f.CopyControls(this);
    }
    
    public void CopyControl(Control parentControl) {
        foreach (Control cntrl in parentControl.Controls) {
            Type t = GetType(cntrl);
            Control c = t を元に Control オブジェクトのインスタンスを生成する
            cntrl の属性を c にコピーする
            Position なんかはインスタンスを作って設定すること
            if (parentControl is typeof(Form)) {
                this.Controls.Add(c);
            } ekse {
                parentControl.Controls.Add(c);
            }
            if (c.Controls.Count > 0) {
                CopyControl(c);
            }
        }
        // どこかで、この Form 上にコントロールがないことを確認するべき
    // フォームの大きさをコピーしなきゃダメかも }

    元のコードのように、Form.Controls に追加すると、すべてのコントロールが Form の上に直接乗ることになります。これでは、追加した順番によって、他のコントロールの下に潜り込んだり、Docking 属性が設定されているコントロールでは配置がおかしくなったりします。コントロールの親子関係もそのままコピーしなければなりません。


    Jitta@わんくま同盟
    2010年1月14日 13:16

すべての返信

  • ウィンドウの親は常に一つでなければなりません。Windows.Forms ではボタンやテキストボックスもウィンドウです。
    ご呈示されているコードでは「親変更」になるのです。

    そういうことをしたいなら、単にフォーム1のインスタンスをもう一つ作ればよいのではないですか?

    2010年1月13日 3:04
  • 現在、二つのフォームでコントロールをコピーすると言うことをやっているのですが、どうしてもMoveになってしまいます(オブジェクトである以上は参照渡しになってしまってるのでしょうか)
    その通りで、参照渡しになっています。ディープコピーする必要があります。一番簡単なのはシリアライズして、そこからデシリアライズする方法ですが、コントロールはシリアライズ不可ですので、そのままではこの方法を取ることができません。これを可能にするにはISerializationSurrogateインターフェースを利用したりしますが、敷居は高いと思います。

    (参考)
    MSDN Magazine Archivesは宝の山
    http://d.hatena.ne.jp/Kazzz/20051118/p1


    シャローコピーを実現するMemberwiseCloneメソッドを使用し、必要な参照型のみを自分でコピーする実装を書くことによって、部分的にディープコピーを実現しても良いかもしれません。

    (参考)
    シャローコピー と ディープコピー
    http://blogs.wankuma.com/jeanne/archive/2006/04/07/22287.aspx

    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2010年1月13日 5:27
    モデレータ
  • 現在、二つのフォームでコントロールをコピーすると言うことをやっているのですが、どうしてもMoveになってしまいます(オブジェクトである以上は参照渡しになってしまってるのでしょうか)
    その通りで、参照渡しになっています。ディープコピーする必要があります。一番簡単なのはシリアライズして、そこからデシリアライズする方法ですが、コントロールはシリアライズ不可ですので、そのままではこの方法を取ることができません。これを可能にするにはISerializationSurrogateインターフェースを利用したりしますが、敷居は高いと思います。
    コントロールのウィンドウハンドルを別の値にしないといけないはずなので、仮にディープコピーやシャローコピーで値のコピーを行ったとしても、「ハンドルの値が同じ」であるかぎり、コントロールは移動されるはずです。

    Control を Add するという処理は、若干ややこしい事になってます。

    2010年1月13日 6:57
  • コントロールのウィンドウハンドルを別の値にしないといけないはずなので、仮にディープコピーやシャローコピーで値のコピーを行ったとしても、「ハンドルの値が同じ」であるかぎり、コントロールは移動されるはずです。

    Control を Add するという処理は、若干ややこしい事になってます。

    なるほど。そういう事情があるのですね。ぼんやりとした感想ですが、コントロールがシリアライズ不可になっているのは、この辺りの仕組みにも関係しているかもしれませんね。
    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://blogs.wankuma.com/trapemiya/
    2010年1月13日 7:47
    モデレータ
  • 「コピーをしたい」ということなのに、何故「移動」になってしまうか。それは、「コピーを作っていないから」です。

    
    private void button2_Click(object sender, EventArgs e) {
        f.Show();
        // ここでコントロールを取り出し、
        Control[] all = GetAllControls(this);
        // ここで“参照させて”いる
        f.C_a = all;
        f.SetControl2();
    }
    
    // こちらは「参照の値渡し」
    // もし、「値の値渡し」をやったなら、
    // Form1 に同じコントロールが2つ出来ることになる
    public void SetControl(Control c) {
        Controls.Add(c);
    }
    
    public void SetControl2() {
    	// ここで親のすげ替えをやっているので「移動」になる
    	// 囚人さんが提示されている、囚人さんのブログ参照
        foreach (Control c in g_c) {
            Controls.Add(c);
        }
    }
    

    コピーするなら、こんな感じ。

    
    private void button2_Click(object sender, EventArgs e) {
        f.Show();
        f.CopyControls(this);
    }
    
    public void CopyControl(Control parentControl) {
        foreach (Control cntrl in parentControl.Controls) {
            Type t = GetType(cntrl);
            Control c = t を元に Control オブジェクトのインスタンスを生成する
            cntrl の属性を c にコピーする
            Position なんかはインスタンスを作って設定すること
            if (parentControl is typeof(Form)) {
                this.Controls.Add(c);
            } ekse {
                parentControl.Controls.Add(c);
            }
            if (c.Controls.Count > 0) {
                CopyControl(c);
            }
        }
        // どこかで、この Form 上にコントロールがないことを確認するべき
    // フォームの大きさをコピーしなきゃダメかも }

    元のコードのように、Form.Controls に追加すると、すべてのコントロールが Form の上に直接乗ることになります。これでは、追加した順番によって、他のコントロールの下に潜り込んだり、Docking 属性が設定されているコントロールでは配置がおかしくなったりします。コントロールの親子関係もそのままコピーしなければなりません。


    Jitta@わんくま同盟
    2010年1月14日 13:16