DOBON.NET DOBON.NETプログラミング掲示板過去ログ

異なったコントロール間で画像をドラッグ&ドロップしてコピーを行うには

環境/言語:[XP,C#]
分類:[.NET]

いつも参考にさせていただいてます。

下記サイトを
http://dobon.net/vb/dotnet/control/draganddrop.html
参考にしてドラッグ&ドロップのプログラムを行いました。

・目標
フォームに2つのパネルを貼り、パネル1上にはピクチャボックスが貼ってある。
パネル1上にあるピクチャボックスをD&Dすることでパネル2にコピーをしたい。

・現状
パネル1上にあるピクチャボックスをD&Dしたらパネル2にピクチャボックスが表示されたが、
パネル1上には何もなくなってしまった。(つまりムーブ状態)

・質問
D&Dした結果両方ともピクチャボックスが表示されるようにするにはどのようにすればよいのでしょうか?
以下に、それぞれのイベントハンドラのソースを記します。
アドバイスよろしくお願いいたします。

private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
//マウスの左ボタンだけが押されている時のみドラッグできるようにする
if (e.Button == MouseButtons.Left)
{
//ドラッグの準備
PictureBox pic = (PictureBox) sender;
//マウスの押された位置を記憶
mouseDownPoint = new Point(e.X, e.Y);
}
else
mouseDownPoint = Point.Empty;
}

private void pictureBox1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
mouseDownPoint = Point.Empty;
}

private void pictureBox1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (mouseDownPoint != Point.Empty)
{
//ドラッグとしないマウスの移動範囲を取得する
Rectangle moveRect = new Rectangle(
mouseDownPoint.X - SystemInformation.DragSize.Width / 2,
mouseDownPoint.Y - SystemInformation.DragSize.Height / 2,
SystemInformation.DragSize.Width,
SystemInformation.DragSize.Height);
//ドラッグとする移動範囲を超えたか調べる
if (!moveRect.Contains(e.X, e.Y))
{
//ドラッグの準備
PictureBox pic = (PictureBox) sender;

//ドラッグ&ドロップ処理を開始する
DragDropEffects dde =
pic.DoDragDrop(this.pictureBox1,
DragDropEffects.Copy);

mouseDownPoint = Point.Empty;
}
}
}

private void panel2_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
{
//ドロップされたデータがPictureBox型か調べる
if (e.Data.GetDataPresent(typeof(PictureBox)))
{
PictureBox pic = (PictureBox)e.Data.GetData(typeof(PictureBox));
//ドロップされたデータをリストボックスに追加する
this.panel2.Controls.Add(pic);
}
}

private void panel2_DragOver(object sender, System.Windows.Forms.DragEventArgs e)
{
//ドラッグされているデータがPictureBox型か調べる
if (e.Data.GetDataPresent(typeof(PictureBox)))
{
e.Effect = DragDropEffects.Copy;
}
else
//PictureBox型でなければ受け入れない
e.Effect = DragDropEffects.None;
}


見にくくなってすみません
ある一つのコントロールは、一つの親の元にしか存在できません。別の親に入れるには元の親と縁を切る必要があります。
ですので、必要なデータを読みとってコピー先でコントロールを新しく作成(または既存のを置き換え)してあげなければなりません。
画像(Image オブジェクト)なんかも、複数のコントロールに一つの実体を属させると混乱の元ですので、Clone するなりして別オブジェクトにする事を勧めます。
ある一つのコントロールは、一つの親の元にしか存在できません。別の親に入れるには元の親と縁を切る必要があります。
ですので、必要なデータを読みとってコピー先でコントロールを新しく作成(または既存のを置き換え)してあげなければなりません。
画像(Image オブジェクト)なんかも、複数のコントロールに一つの実体を属させると混乱の元ですので、Clone するなりして別オブジェクトにする事を勧めます。
> //ドロップされたデータをリストボックスに追加する
> this.panel2.Controls.Add(pic);

おっしゃるとおりこれはPictureBoxコントロールそのものを移動しています。

今回の場合、イメージを複製して相手のImageプロパティなどに設定する必要があるでしょう。
Image.Clone メソッドや
こちらどぼんさんのTipsの「画像:クリップボードに画像をコピーする」を参考にしてみてください。

※ってもっとシンプルなやり方があるような。。。
> //ドロップされたデータをリストボックスに追加する
> this.panel2.Controls.Add(pic);

おっしゃるとおりこれはPictureBoxコントロールそのものを移動しています。

今回の場合、イメージを複製して相手のImageプロパティなどに設定する必要があるでしょう。
Image.Clone メソッドや
こちらどぼんさんのTipsの「画像:クリップボードに画像をコピーする」を参考にしてみてください。

※ってもっとシンプルなやり方があるような。。。
Hongliangさん、まどかさん、ありがとうございます。

PictureBoxはImageをCloneしたらコピーすることができました。
picClone.Image= (System.Drawing.Image)pic.Image.Clone();
this.panel2.Controls.Add(picClone);

別オブジェクトにする必要があったのですね。


これを応用して、自作のクラス(UserControlにPictureBoxやLabelなど貼り付けたもの)で試しました。

自作クラスにICloneableを実装させてCloneメソッドにMemberwiseClone()を返すようにしたのですが、
DragDropイベント時に
MyClass mc = (MyClass)e.Data.GetData(typeof(MyClass)); //コピー元

MyClass mcClone = new MyClass(); //コピー先を新規作成
mcClone = (MyClass)mc.Clone();
try
{
this.panel2.Control.Add(mcClone);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}

「'子'はこの親の子コントロールではありません。」とキャッチしてしまいました。
これは、Hongliangさんの言われる縁切りができていないということなのでしょうか?

ちなみに、CloneExメソッドを自作し、新しくNewしてメンバを代入させました。(手動コピー)
MyClass mcClone = new MyClass();
mcClone.m_intID = this.m_intID;
mcClone.m_strText = this.m_strText;
return mcClone;
として、Add(mcClone)したらコピーできました。
ディープコピーの必要があったのかな?って思っています。
Hongliangさん、まどかさん、ありがとうございます。

PictureBoxはImageをCloneしたらコピーすることができました。
picClone.Image= (System.Drawing.Image)pic.Image.Clone();
this.panel2.Controls.Add(picClone);

別オブジェクトにする必要があったのですね。


これを応用して、自作のクラス(UserControlにPictureBoxやLabelなど貼り付けたもの)で試しました。

自作クラスにICloneableを実装させてCloneメソッドにMemberwiseClone()を返すようにしたのですが、
DragDropイベント時に
MyClass mc = (MyClass)e.Data.GetData(typeof(MyClass)); //コピー元

MyClass mcClone = new MyClass(); //コピー先を新規作成
mcClone = (MyClass)mc.Clone();
try
{
this.panel2.Control.Add(mcClone);
}
catch(Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}

「'子'はこの親の子コントロールではありません。」とキャッチしてしまいました。
これは、Hongliangさんの言われる縁切りができていないということなのでしょうか?

ちなみに、CloneExメソッドを自作し、新しくNewしてメンバを代入させました。(手動コピー)
MyClass mcClone = new MyClass();
mcClone.m_intID = this.m_intID;
mcClone.m_strText = this.m_strText;
return mcClone;
として、Add(mcClone)したらコピーできました。
ディープコピーの必要があったのかな?って思っています。
どうも、Controls.Addに固執しいらっしゃるようですが、ドラッグ先にもPictureBoxを貼り付けておき
Imageプロパティの設定だけではだめなのですか?

またコントロールをドラッグしているようですが、そのD&Dに必要な「データ」のみにしたほうがよいと思います。


> MyClass mcClone = new MyClass(); //コピー先を新規作成
> mcClone = (MyClass)mc.Clone();

Cloneは複製という「インスタンス」を返しますので、受ける変数をNew(インスタンス化)する必要は無いです。
どうも、Controls.Addに固執しいらっしゃるようですが、ドラッグ先にもPictureBoxを貼り付けておき
Imageプロパティの設定だけではだめなのですか?

またコントロールをドラッグしているようですが、そのD&Dに必要な「データ」のみにしたほうがよいと思います。


> MyClass mcClone = new MyClass(); //コピー先を新規作成
> mcClone = (MyClass)mc.Clone();

Cloneは複製という「インスタンス」を返しますので、受ける変数をNew(インスタンス化)する必要は無いです。
まどかさん、
貴重なアドバイスありがとうございます。

なるほど、D&D先にもコントロールを用意する手もありましたね。
今考えていた目標は、不特定数のD&Dを受け付けるようにしたかったため、
その都度Control.Addさせていました。

> またコントロールをドラッグしているようですが、そのD&Dに必要な「データ」のみにしたほうがよいと思います。

これは、パフォーマンス上(オーバーヘッドが大きい)、プログラミング上マズいことが起きるからでしょうか?
確かにデータだけ渡せばコピーできそうなので参考になります。

このときは、コントロールにピクチャやテキストデータを搭載させていたので、
このコントロールをデータのクラスと見立てて渡してました。
改めて渡すデータ用のクラスを作成してそのクラスを渡そうと思います。
まどかさん、
貴重なアドバイスありがとうございます。

なるほど、D&D先にもコントロールを用意する手もありましたね。
今考えていた目標は、不特定数のD&Dを受け付けるようにしたかったため、
その都度Control.Addさせていました。

> またコントロールをドラッグしているようですが、そのD&Dに必要な「データ」のみにしたほうがよいと思います。

これは、パフォーマンス上(オーバーヘッドが大きい)、プログラミング上マズいことが起きるからでしょうか?
確かにデータだけ渡せばコピーできそうなので参考になります。

このときは、コントロールにピクチャやテキストデータを搭載させていたので、
このコントロールをデータのクラスと見立てて渡してました。
改めて渡すデータ用のクラスを作成してそのクラスを渡そうと思います。

DOBON.NET | プログラミング道 | プログラミング掲示板