同じWindowsフォームアプリケーションのインスタンス間でドラッグアンドドロップ
-
05-07-2019 - |
質問
ドラッグアンドドロップコードを試すための小さなWindowsフォームテストアプリケーションを作成しました。フォームは3つのPictureBoxで構成されています。私の意図は、1つのPictureBoxから画像を取得し、ドラッグ操作中にカスタムカーソルとして表示し、別のPictureBoxターゲットにドロップすることでした。
これは、同じフォーム上にある限り、あるPictureBoxから別のPictureBoxでも問題なく動作します。
同じアプリケーションの2つのインスタンスを開き、それらの間をドラッグ/ドロップしようとすると、次の不可解なエラーが表示されます:
このリモートプロキシにはチャネルがありません シンクは、サーバーが 登録されているサーバーチャネルはありません リスニング、またはこのアプリケーションにはない に適切なクライアントチャネル サーバー。
ただし、何らかの理由で、ワードパッドにドラッグ/ドロップしても動作します(ただし、MS Wordやペイントブラシは動作しません)。
3つのPictureBoxは、次のようにイベントをフックします:
foreach (Control pbx in this.Controls) {
if (pbx is PictureBox) {
pbx.AllowDrop = true;
pbx.MouseDown += new MouseEventHandler(pictureBox_MouseDown);
pbx.GiveFeedback += new GiveFeedbackEventHandler(pictureBox_GiveFeedback);
pbx.DragEnter += new DragEventHandler(pictureBox_DragEnter);
pbx.DragDrop += new DragEventHandler(pictureBox_DragDrop);
}
}
次に、このような4つのイベントがあります:
void pictureBox_MouseDown(object sender, MouseEventArgs e) {
int width = (sender as PictureBox).Image.Width;
int height = (sender as PictureBox).Image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
g.DrawImage((sender as PictureBox).Image, 0, 0, width, height);
g.Dispose();
cursorCreatedFromControlBitmap = CustomCursors.CreateFormCursor(bmp, transparencyType);
bmp.Dispose();
Cursor.Current = this.cursorCreatedFromControlBitmap;
(sender as PictureBox).DoDragDrop((sender as PictureBox).Image, DragDropEffects.All);
}
void pictureBox_GiveFeedback(object sender, GiveFeedbackEventArgs gfea) {
gfea.UseDefaultCursors = false;
}
void pictureBox_DragEnter(object sender, DragEventArgs dea) {
if ((dea.KeyState & 32) == 32) { // ALT is pressed
dea.Effect = DragDropEffects.Link;
}
else if ((dea.KeyState & 8) == 8) { // CTRL is pressed
dea.Effect = DragDropEffects.Copy;
}
else if ((dea.KeyState & 4) == 4) { // SHIFT is pressed
dea.Effect = DragDropEffects.None;
}
else {
dea.Effect = DragDropEffects.Move;
}
}
void pictureBox_DragDrop(object sender, DragEventArgs dea) {
if (((IDataObject)dea.Data).GetDataPresent(DataFormats.Bitmap))
(sender as PictureBox).Image = (Image)((IDataObject)dea.Data).GetData(DataFormats.Bitmap);
}
ご協力いただければ幸いです!
解決
歯をかじり、髪を引っ張った後、私は実行可能な解決策を思いつくことができました。 .NETとそのOLEドラッグアンドドロップのサポートにより、文書化されていない奇妙なことが起こっているようです。 .NETアプリケーション間でドラッグアンドドロップを実行するときに.NETリモーティングを使用しようとしているようですが、これはどこにも文書化されていますか?いいえ、そうは思いません。
したがって、私が思いついた解決策は、プロセス間でビットマップデータをマーシャリングするヘルパークラスを含みます。まず、これがクラスです。
[Serializable]
public class BitmapTransfer
{
private byte[] buffer;
private PixelFormat pixelFormat;
private Size size;
private float dpiX;
private float dpiY;
public BitmapTransfer(Bitmap source)
{
this.pixelFormat = source.PixelFormat;
this.size = source.Size;
this.dpiX = source.HorizontalResolution;
this.dpiY = source.VerticalResolution;
BitmapData bitmapData = source.LockBits(
new Rectangle(new Point(0, 0), source.Size),
ImageLockMode.ReadOnly,
source.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
int bufferSize = bitmapData.Stride * bitmapData.Height;
this.buffer = new byte[bufferSize];
System.Runtime.InteropServices.Marshal.Copy(ptr, buffer, 0, bufferSize);
source.UnlockBits(bitmapData);
}
public Bitmap ToBitmap()
{
Bitmap bitmap = new Bitmap(
this.size.Width,
this.size.Height,
this.pixelFormat);
bitmap.SetResolution(this.dpiX, this.dpiY);
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(new Point(0, 0), bitmap.Size),
ImageLockMode.WriteOnly, bitmap.PixelFormat);
IntPtr ptr = bitmapData.Scan0;
int bufferSize = bitmapData.Stride * bitmapData.Height;
System.Runtime.InteropServices.Marshal.Copy(this.buffer, 0, ptr, bufferSize);
bitmap.UnlockBits(bitmapData);
return bitmap;
}
}
ビットマップの.NETおよびアンマネージ受信者の両方をサポートする方法でクラスを使用するには、次のようにDataObjectクラスをドラッグアンドドロップ操作に使用します。
ドラッグ操作を開始するには:
DataObject dataObject = new DataObject();
dataObject.SetData(typeof(BitmapTransfer),
new BitmapTransfer((sender as PictureBox).Image as Bitmap));
dataObject.SetData(DataFormats.Bitmap,
(sender as PictureBox).Image as Bitmap);
(sender as PictureBox).DoDragDrop(dataObject, DragDropEffects.All);
操作を完了するには:
if (dea.Data.GetDataPresent(typeof(BitmapTransfer)))
{
BitmapTransfer bitmapTransfer =
(BitmapTransfer)dea.Data.GetData(typeof(BitmapTransfer));
(sender as PictureBox).Image = bitmapTransfer.ToBitmap();
}
else if(dea.Data.GetDataPresent(DataFormats.Bitmap))
{
Bitmap b = (Bitmap)dea.Data.GetData(DataFormats.Bitmap);
(sender as PictureBox).Image = b;
}
顧客のBitmapTransferのチェックが最初に実行されるため、データオブジェクト内の通常のビットマップの存在よりも優先されます。 BitmapTransferクラスは、複数のアプリケーションで使用するために共有ライブラリに配置できます。アプリケーション間のドラッグアンドドロップのように、シリアル化可能としてマークする必要があります。アプリケーション内、アプリケーション間、および.NETアプリケーションからワードパッドへのビットマップのドラッグアンドドロップでテストしました。
これがお役に立てば幸いです。
他のヒント
最近、この問題に遭遇し、クリップボードでカスタム形式を使用していたため、相互運用が少し難しくなりました。とにかく、少し光を反射して、元のSystem.Windows.Forms.DataObjectを取得し、GetDataを呼び出して、通常のようにカスタムアイテムを取得することができました。
var oleConverterType = Type.GetType("System.Windows.DataObject+OleConverter, PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35");
var oleConverter = typeof(System.Windows.DataObject).GetField("_innerData", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(e.Data);
var dataObject = (System.Windows.Forms.DataObject)oleConverterType.GetProperty("OleDataObject").GetValue(oleConverter, null);
var item = dataObject.GetData(this.Format);
耳から蒸気が出るのに何時間もイライラした後、私はついにこの問題の2番目の解決策にたどり着きました。正確にどのソリューションが最もエレガントであるかは、おそらく見る人の目にあります。 Michaelと私のソリューションが、フラストレーションのあるプログラマーを助け、同様のクエストに乗り出す時間を節約することを願っています。
まず、私を驚かせた1つのことは、Wordpadが箱から出してすぐにドラッグ/ドロップ画像を受信できることでした。したがって、ファイルのパッケージ化はおそらく問題ではありませんでしたが、おそらく受信側で何か怪しいことが起こっていました。
そして魚臭いことがありました。 .Netフレームワークの周りに浮かぶIDataObjectsのいくつかのタイプがあることがわかります。 Michaelが指摘したように、OLEドラッグアンドドロップサポートは、アプリケーション間でやり取りするときに.Netリモーティングを使用しようとします。これにより、実際にはSystem.Runtime.Remoting.Proxies .__ TransparentProxyがイメージのある場所に配置されます。明らかにこれは(完全に)正しくありません。
次の記事は、正しい方向へのいくつかの指針を与えてくれました。
WindowsフォームのデフォルトはSystem.Windows.Forms.IDataObjectです。ただし、ここではさまざまなプロセスを扱っているため、代わりにSystem.Runtime.InteropServices.ComTypes.IDataObjectを試してみることにしました。
ドラッグドロップイベントでは、次のコードが問題を解決します。
const int CF_BITMAP = 2;
System.Runtime.InteropServices.ComTypes.FORMATETC formatEtc;
System.Runtime.InteropServices.ComTypes.STGMEDIUM stgMedium;
formatEtc = new System.Runtime.InteropServices.ComTypes.FORMATETC();
formatEtc.cfFormat = CF_BITMAP;
formatEtc.dwAspect = System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT;
formatEtc.lindex = -1;
formatEtc.tymed = System.Runtime.InteropServices.ComTypes.TYMED.TYMED_GDI;
2つのGetData関数は、同じ名前のみを共有します。 1つはオブジェクトを返し、もう1つはvoidを返すように定義されており、代わりに情報をstgMedium out パラメーターに渡します。
(dea.Data as System.Runtime.InteropServices.ComTypes.IDataObject).GetData(ref formatEtc, out stgMedium);
Bitmap remotingImage = Bitmap.FromHbitmap(stgMedium.unionmember);
(sender as PictureBox).Image = remotingImage;
最後に、メモリリークを回避するには、OLE関数ReleaseStgMediumを呼び出すことをお勧めします。
ReleaseStgMedium(ref stgMedium);
この関数は次のように含めることができます:
[DllImport("ole32.dll")]
public static extern void ReleaseStgMedium([In, MarshalAs(UnmanagedType.Struct)] ref System.Runtime.InteropServices.ComTypes.STGMEDIUM pmedium);
...このコードは、2つのアプリケーション間の(ビットマップの)ドラッグアンドドロップ操作で完全に機能するようです。コードは、他の有効なクリップボード形式やおそらくカスタムクリップボード形式にも簡単に拡張できます。パッケージングパーツでは何も行われていないため、画像をワードパッドにドラッグアンドドロップできます。ビットマップ形式を受け入れるため、Wordからアプリケーションに画像をドラッグすることもできます。
補足として、IEから画像を直接ドラッグアンドドロップしても、DragDropイベントは発生しません。奇妙な。
奇妙なことに、DragDropメソッドで、DragEventArgsからビットマップイメージを取得できるかどうかをテストしたことがありますか?送信者キャストを行わずに? pictureboxオブジェクトがシリアル化可能ではないので、別のアプリドメインで送信者を使用しようとすると問題が発生するのではないかと思っています...