2009-07-29 82 views
16

我創建了一個小型Windows窗體測試應用程序來嘗試一些拖放代碼。該表單由三個PictureBox組成。我的意圖是從一個PictureBox中抓取圖片,在拖動操作中將其顯示爲自定義光標,然後將其放到另一個PictureBox目標上。在同一Windows窗體應用程序的實例之間拖放

從一個PictureBox到另一個的工作正常,只要它們的形式相同

如果我打開同一個應用程序的兩個實例,並試圖拖動它們之間/降,我得到以下神祕的錯誤:

This remoting proxy has no channel sink which means either the server has no registered server channels that are listening, or this application has no suitable client channel to talk to the server.

出於某種原因,但是,它的工作拖動/降寫字板(但不是MS Word或畫筆)。

三個PictureBoxes得到他們的活動迷上了這樣的:

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); 
    } 
} 

然後有四個這樣的活動:

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); 
} 

任何幫助將不勝感激!

回答

10

經過多次咬牙切齒和拉扯頭髮之後,我才能夠想出一個可行的解決方案。看起來在.NET和它的OLE拖放支持下有一些沒有記錄的奇怪現象。在執行.NET應用程序之間的拖放時,它似乎試圖使用.NET Remoting,但是這在任何地方都有記錄?不,我不這麼認爲。

所以我想出的解決方案涉及到一個幫助類來幫助編組進程之間的位圖數據。首先,這是班級。

[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 = 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應用程序到寫字板的拖放位圖來測試它。

希望這可以幫助你。

+0

很棒的回答。很酷。 +1 – 2009-07-30 16:31:29

+0

嗨邁克爾! 我喜歡你的方法。感謝你的回答!這已經困擾了我很長一段時間,你的解決方案是一個很好的解決方案,以解決一個反覆出現的問題。但是,我發現了另一種解決方案,在傳輸常用剪貼板格式的情況下可能會更好(至少更短)。該解決方案如下所述。無論如何,我想給你「接受的答案」的信譽,因爲你的解決方案可能更適用於一般情況。請查看下面的解決方案,以解決此問題的另一種方法。 - Peder - – Pedery 2009-07-31 16:30:32

+0

您的發現非常有趣,可能是您的案例的最佳解決方案。我完全打算深入研究這篇文章並嘗試這種技術。我們兩方都需要這樣的努力來解決這個問題,這有點令人傷心。任何時候我都需要與.NET的shell進行交互,因爲婚姻通常是非常棘手的。感謝您的信任,甚至更多關於此主題的更多信息。 – 2009-07-31 17:33:15

1

出於好奇,在DragDrop方法中,您是否嘗試過測試是否可以從DragEventArgs中獲取位圖圖像?沒有做發件人演員?我想知道picturebox對象是不是可序列化的,當您嘗試在不同的應用程序域中使用發件人時會導致問題...

+0

我試着創建一個完全獨立的位圖,並用它來代替。同樣的結果。可以在內部拖放,而不是使用單獨的應用程序。 要回答你的問題,是的,在DragDrop事件中,圖像確實以位圖的形式出現。只是應用程序崩潰與上述錯誤。 – Pedery 2009-07-29 18:47:52

6

隨着蒸汽從我耳邊流出,我經歷了幾小時和幾個小時的挫折之後,終於找到了解決這個問題的第二個解決方案。究竟哪種解決方案最優雅可能在旁觀者眼中。我希望邁克爾和我的解決方案能夠幫助受挫的程序員,並在他們開始類似的任務時爲他們節省時間。

首先,有一件事情讓我印象深刻,那就是寫字板能夠在開箱即可接收拖放圖像。因此,文件的打包可能不是問題,但在接收端也許有些可疑的事情發生。

還有魚腥味。事實證明,有關於.Net框架的IDataObjects有很多種類。正如Michael指出的那樣,OLE拖放支持在應用程序之間進行交互時嘗試使用.Net遠程處理。這實際上把圖像應該放在一個System.Runtime.Remoting.Proxies .__ TransparentProxy。顯然這不是(完全)正確的。

下面的文章給了我在正確的方向有幾點建議:

http://blogs.msdn.com/adamroot/archive/2008/02/01/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx

Windows窗體默認爲System.Windows.Forms.IDataObject。但是,因爲我們在這裏處理不同的進程,所以我決定給System.Runtime.InteropServices.ComTypes.IDataObject一個鏡頭。

在DragDrop事件,下面的代碼解決了這個問題:

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; 

兩個的GetData功能只共享相同的名稱。一個返回一個對象,對方被定義爲返回void,而是傳遞信息到STGMEDIUM 參數:

(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); 

...這代碼似乎每工作很容易在兩個應用程序之間進行拖放操作(位圖)。該代碼可以很容易地擴展到其他有效的剪貼板格式,也可能自定義剪貼板格式。由於封裝部分沒有任何工作,因此您仍然可以將圖像拖放到寫字板,並且由於它接受位圖格式,因此也可以將圖像從Word拖入應用程序中。

作爲一個方面說明,直接從IE拖放圖像甚至不會引發DragDrop事件。奇怪。

+0

<<作爲一個方面說明,直接從IE拖放圖像甚至不會引發DragDrop事件。>>哪個操作系統?在Vista +上,已經完成了一些工作來防止Internet區域的內容被丟棄到不知情的應用程序。您必須在註冊表中註冊您的應用程序以接受從IE瀏覽器中刪除。 – EricLaw 2010-01-24 20:24:59

7

我最近遇到了這個問題,並在剪貼板中使用自定義格式,使得Interop更加困難。無論如何,通過一些光線反射,我可以獲得原始的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); 
+0

有趣。你認爲你的方法是「安全的」,這意味着它不會在某些計算機上工作,並在其他計算機上出現問題? – Pedery 2009-08-13 14:59:51

+0

唯一的潛在問題是獲取OleConverter類型,它應該始終與PresentationCore一起出現,因此可以通過使用您知道將在PresentationCore中獲取完全合格的程序集名稱的類型來更安全。但除此之外,它應該沒有問題。 – dariusriggins 2009-08-13 22:14:24

+0

你可以嘗試做一個簡單的轉換來讓Interop IDataObject: (System.Runtime.InteropServices.ComTypes.IDataObject)e.Data – 2013-09-19 06:55:16

相關問題