2009-07-29 24 views
16

Tôi đã tạo một ứng dụng thử nghiệm Biểu mẫu Windows nhỏ để thử một số mã kéo/thả. Biểu mẫu bao gồm ba PictureBox. Ý định của tôi là lấy một hình ảnh từ một PictureBox, hiển thị nó như một con trỏ tùy chỉnh trong quá trình kéo, sau đó thả nó vào một mục tiêu PictureBox khác.Kéo và thả giữa các trường hợp của cùng một Ứng dụng Biểu mẫu Windows

Tính năng này hoạt động tốt từ một PictureBox sang một PictureBox khác miễn là chúng ở trên cùng một hình thức.

Nếu tôi mở hai trường hợp của cùng một ứng dụng và cố gắng để kéo/thả giữa chúng, tôi nhận được báo lỗi khó hiểu sau:

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.

Đối với một số lý do, tuy nhiên, nó không làm việc để kéo/thả để Wordpad (nhưng không phải MS Word hoặc Paintbrush).

Ba PictureBoxes nhận các sự kiện của họ nối như thế này:

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

Sau đó, có bốn sự kiện như thế này:

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

Mọi trợ giúp sẽ được đánh giá cao!

Trả lời

10

Sau nhiều lần nghiến răng và kéo tóc, tôi có thể đưa ra giải pháp khả thi. Dường như có một số sự kỳ lạ không có giấy tờ đang diễn ra dưới sự che chở của .NET và sự hỗ trợ kéo và thả OLE của nó. Dường như nó đang cố sử dụng tính năng truy cập từ xa .NET khi thực hiện kéo và thả giữa các ứng dụng .NET, nhưng liệu tài liệu này có ở bất kỳ đâu không? Không, tôi không nghĩ vậy.

Vì vậy, giải pháp tôi đưa ra liên quan đến lớp trợ giúp để so sánh dữ liệu bitmap giữa các quy trình. Đầu tiên, đây là lớp học.

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

Để sử dụng lớp theo cách hỗ trợ cả người dùng .NET và không được quản lý bitmap, lớp DataObject được sử dụng cho thao tác kéo và thả như sau.

Để bắt đầu hoạt động kéo:

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

Để hoàn thành hoạt động:

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

Vui lòng cung cho khách hàng BitmapTransfer được thực hiện đầu tiên nên nó được ưu tiên hơn sự tồn tại của một Bitmap thường xuyên trong đối tượng dữ liệu. Lớp BitmapTransfer có thể được đặt trong một thư viện chia sẻ để sử dụng với nhiều ứng dụng. Nó phải được đánh dấu tuần tự hóa như được hiển thị để kéo và thả giữa các ứng dụng.Tôi đã thử nghiệm nó bằng cách kéo và thả các bitmap trong một ứng dụng, giữa các ứng dụng và từ một ứng dụng .NET đến Wordpad.

Hy vọng điều này sẽ giúp bạn.

+0

Câu trả lời hay. Rất tuyệt. +1 –

+0

Xin chào Michael! Tôi thích cách tiếp cận của bạn. Cảm ơn câu trả lời! Điều này đã làm phiền tôi một thời gian và giải pháp của bạn là một giải pháp tốt cho một vấn đề định kỳ. Tuy nhiên, tôi đã tìm thấy một giải pháp mà có thể tốt hơn (ít nhất là ngắn hơn) trong trường hợp trasfering định dạng clipboard phổ biến. Giải pháp đó được mô tả dưới đây. Trong mọi trường hợp, tôi muốn cung cấp cho bạn câu trả lời "chấp nhận câu trả lời" vì giải pháp của bạn có thể dễ chấp nhận hơn đối với trường hợp chung. Vui lòng xem lại giải pháp của tôi bên dưới để biết cách khác để giải quyết vấn đề này. - Peder - – Pedery

+0

Tìm thấy của bạn rất thú vị và có lẽ là giải pháp tốt nhất cho trường hợp của bạn. Tôi hoàn toàn có ý định đào sâu vào bài viết đó và thử nghiệm với kỹ thuật này. Đó là một chút buồn rằng nỗ lực đó là cần thiết trên cả hai bộ phận của chúng tôi để giải quyết điều này. Tôi cringe bất cứ lúc nào tôi cần phải tương tác với vỏ từ .NET như hôn nhân thường là một trong những đá. Cảm ơn tín dụng và thậm chí nhiều hơn cho các thông tin bổ sung về chủ đề này. –

1

Chỉ cần tò mò, trong phương pháp DragDrop, bạn đã thử kiểm tra xem bạn có thể lấy hình ảnh bitmap ra khỏi DragEventArgs không? Mà không làm người gửi diễn viên? Tôi tự hỏi liệu đối tượng picturebox có thể tuần tự hóa không, có thể gây ra sự cố khi bạn cố gắng sử dụng người gửi trong miền ứng dụng khác ...

+0

Tôi đã thử tạo bitmap hoàn toàn riêng biệt và sử dụng thay thế đó. Cùng một kết quả. Có thể kéo/thả nội bộ và không hoạt động với một ứng dụng riêng biệt. Để trả lời câu hỏi của bạn, vâng, trong sự kiện DragDrop, hình ảnh này xuất hiện dưới dạng bitmap. Nó chỉ là ứng dụng treo với lỗi nói trên. – Pedery

6

Sau giờ và giờ thất vọng với hơi thở ra khỏi tai tôi, cuối cùng tôi đã đến giải pháp thứ hai cho vấn đề này. Chính xác thì giải pháp nào là thanh lịch nhất có lẽ là trong mắt của kẻ đối xử. Tôi hy vọng rằng Michael và các giải pháp của tôi sẽ giúp các lập trình viên thất vọng và tiết kiệm thời gian khi họ bắt tay vào các nhiệm vụ tương tự.

Trước hết, một trong những điều đã gây ấn tượng với tôi là Wordpad có thể nhận được hình ảnh kéo/thả vừa ra khỏi hộp. Vì vậy, bao bì của tập tin có lẽ không phải là vấn đề, nhưng có lẽ có điều gì đó cá đang diễn ra ở đầu nhận.

Và có nhiều cá. Hóa ra có những loại IDataObject nổi bật trong khoảng .Net framework. Như Michael đã chỉ ra, OLE kéo và thả các nỗ lực hỗ trợ để sử dụng .Net remoting khi tương tác giữa các ứng dụng. Điều này thực sự đặt một System.Runtime.Remoting.Proxies .__ TransparentProxy nơi hình ảnh được cho là. Rõ ràng đây không phải là (hoàn toàn) chính xác.

Bài viết sau đây đã cho tôi một vài gợi ý đi đúng hướng:

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

Windows Forms mặc định là System.Windows.Forms.IDataObject. Tuy nhiên, vì chúng tôi đang xử lý các quy trình khác nhau ở đây nên tôi đã quyết định cung cấp cho System.Runtime.InteropServices.ComTypes.IDataObject một ảnh thay thế.

Trong trường hợp DragDrop, đoạn code sau giải quyết vấn đề:

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; 

Hai chức năng GetData chỉ chia sẻ cùng tên. Một trả về một đối tượng, khác được định nghĩa để trả về void và thay vào đó đi các thông tin vào stgMedium ra tham số:

(dea.Data as System.Runtime.InteropServices.ComTypes.IDataObject).GetData(ref formatEtc, out stgMedium); 
Bitmap remotingImage = Bitmap.FromHbitmap(stgMedium.unionmember); 

(sender as PictureBox).Image = remotingImage; 

Cuối cùng, để tránh rò rỉ bộ nhớ, nó có thể là một ý tưởng tốt để gọi hàm OLE ReleaseStgMedium :

ReleaseStgMedium(ref stgMedium); 

chức năng đó có thể được bao gồm như sau:

[DllImport("ole32.dll")] 
public static extern void ReleaseStgMedium([In, MarshalAs(UnmanagedType.Struct)] ref System.Runtime.InteropServices.ComTypes.STGMEDIUM pmedium); 

... và mã này dường như làm việc mỗi với các thao tác kéo và thả (của bitmap) giữa hai ứng dụng. Mã này có thể dễ dàng được mở rộng sang các định dạng clipboard hợp lệ khác và có thể là định dạng clipboard tùy chỉnh. Vì không có gì được thực hiện với phần đóng gói, bạn vẫn có thể kéo hình ảnh vào Wordpad và vì nó chấp nhận định dạng bitmap, bạn cũng có thể kéo hình ảnh từ Word vào ứng dụng.

Lưu ý rằng việc kéo và thả hình ảnh trực tiếp từ IE thậm chí không tăng sự kiện DragDrop. Lạ thật.

+0

<< Là một lưu ý phụ, việc kéo và thả hình ảnh trực tiếp từ IE thậm chí không tăng sự kiện DragDrop >> Hệ điều hành nào? Trên Vista +, có một công việc được thực hiện để ngăn chặn nội dung từ vùng Internet bị rơi xuống các ứng dụng không ngờ. Bạn phải đăng ký ứng dụng của bạn trong sổ đăng ký để chấp nhận các giọt ra khỏi IE. – EricLaw

7

Gần đây tôi đã gặp vấn đề này và đang sử dụng định dạng tùy chỉnh trong khay nhớ tạm, làm cho Interop khó hơn một chút. Dù sao, với một chút ánh sáng phản chiếu tôi đã có thể nhận được để System.Windows.Forms.DataObject ban đầu, và sau đó gọi GetData và nhận được mục tùy chỉnh của tôi ra khỏi nó như bình thường.

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

Thú vị. Bạn có nghĩ rằng phương pháp của bạn sẽ "an toàn", có nghĩa là nó sẽ không hoạt động trên một số máy tính và crach trên những người khác? – Pedery

+0

Vấn đề duy nhất có thể là nhận được loại OleConverter, nên nó luôn luôn có mặt với PresentationCore, vì vậy nó có thể được an toàn hơn bằng cách sử dụng một loại mà bạn biết sẽ có trong PresentationCore để có được tên lắp ráp đầy đủ. Nhưng ngoài ra, nó sẽ hoạt động mà không có vấn đề gì. – dariusriggins

+0

bạn có thể thử tạo một dàn diễn viên đơn giản để có Interop IDataObject: (System.Runtime.InteropServices.ComTypes.IDataObject) e.Data –

Các vấn đề liên quan