2009-07-31 44 views
17

Tôi đang cố gắng kéo dữ liệu từ phần Winforms của ứng dụng trên một điều khiển WPF được chứa bên trong một "ElementHost". Và nó đổ vỡ khi tôi cố gắng làm như vậy.WinForms Interop, Kéo và Thả từ WinForms -> WPF

Hãy thử điều tương tự nhưng từ Winforms đến Winforms hoạt động tốt. (Xem mã ví dụ bên dưới)

Tôi cần trợ giúp khi thực hiện công việc này ... có bất kỳ manh mối nào mà tôi đang làm sai không?

Cảm ơn!


Ví dụ:
Trong đoạn code mẫu dưới đây, tôi chỉ cố gắng để kéo một đối tượng tùy chỉnh MyContainerClass tạo ra khi initating kéo vào sự kiểm soát nhãn trên 1) System.Windows.Forms.TextBox (Winforms) và 2) System.Windows.TextBox (WPF, được thêm vào một ElementHost).

Trường hợp 1) hoạt động tốt nhưng trường hợp 2) bị lỗi khi cố truy xuất dữ liệu thả bằng cách sử dụng GetData(). GetDataPresent ("WindowsFormsApplication1.MyContainerClass") trả về "true" vì vậy Về lý thuyết, tôi sẽ có thể lấy lại dữ liệu thả của tôi kiểu như trong Winforms.

Đây là stack trace của vụ tai nạn:

 
"Error HRESULT E_FAIL has been returned from a call to a COM component" with the following stack trace: 
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo) 
at System.Windows.Forms.DataObject.GetDataIntoOleStructs(FORMATETC& formatetc, STGMEDIUM& medium) 
at System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC& formatetc, STGMEDIUM& medium) 
at System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC& formatetc, STGMEDIUM& medium) 
at System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& formatetc, STGMEDIUM& medium) 
at System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(String format, DVASPECT aspect, Int32 index) 
at System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(String format, DVASPECT aspect, Int32 index) 
at System.Windows.DataObject.OleConverter.GetData(String format, Boolean autoConvert, DVASPECT aspect, Int32 index) 
at System.Windows.DataObject.OleConverter.GetData(String format, Boolean autoConvert) 
at System.Windows.DataObject.GetData(String format, Boolean autoConvert) 
at System.Windows.DataObject.GetData(String format) 
at WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e) in WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48 

Dưới đây là một số mã:

// -- Add an ElementHost to your form -- 
// -- Add a label to your form -- 

public partial class Form1 : Form 
{ 
    public Form1() 
    { 
     InitializeComponent(); 

     System.Windows.Controls.TextBox textBox = new System.Windows.Controls.TextBox(); 
     textBox.Text = "WPF TextBox"; 
     textBox.AllowDrop = true; 
     elementHost2.Child = textBox; 
     textBox.PreviewDragEnter += new System.Windows.DragEventHandler(textBox_PreviewDragEnter); 

     System.Windows.Forms.TextBox wfTextBox = new System.Windows.Forms.TextBox(); 
     wfTextBox.Text = "Winforms TextBox"; 
     wfTextBox.AllowDrop = true; 
     wfTextBox.DragEnter += new DragEventHandler(wfTextBox_DragEnter); 
     Controls.Add(wfTextBox); 
    } 

    void wfTextBox_DragEnter(object sender, DragEventArgs e) 
    { 
     bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass"); 

     // NO CRASH here! 
     object data = e.Data.GetData("WindowsFormsApplication1.MyContainerClass"); 
    } 

    void textBox_PreviewDragEnter(object sender, System.Windows.DragEventArgs e) 
    { 
     bool dataPresent = e.Data.GetDataPresent("WindowsFormsApplication1.MyContainerClass"); 

     // Crash appens here!! 
     // {"Error HRESULT E_FAIL has been returned from a call to a COM component."} 
     object data = e.Data.GetData("WindowsFormsApplication1.MyContainerClass"); 
    } 

    private void label1_MouseDown(object sender, MouseEventArgs e) 
    { 
     label1.DoDragDrop(new MyContainerClass(label1.Text), DragDropEffects.Copy); 
    } 
} 

public class MyContainerClass 
{ 
    public object Data { get; set; } 

    public MyContainerClass(object data) 
    { 
     Data = data; 
    } 
} 

Trả lời

16

@Pedery & jmayor: Cảm ơn các bạn đã đề xuất! (xem các phát hiện của tôi dưới đây)

Sau một vài thử nghiệm, thử nghiệm và lỗi, và một chút "Reflector'ing", tôi đã tìm ra chính xác lý do tại sao tôi nhận được thông báo lỗi khó hiểu "Lỗi HRESULT E_FAIL đã được trả về từ một cuộc gọi đến một thành phần COM ".

Đó là do thực tế rằng khi kéo dữ liệu WPF < -> Winforms trong cùng một ứng dụng, dữ liệu đó phải được nối tiếp!

Tôi đã kiểm tra xem sẽ khó khăn như thế nào khi chuyển đổi tất cả các lớp học thành "Serializable" và tôi sẽ có một nỗi đau thực sự vì một vài lý do ...một, chúng tôi sẽ cần phải thực tế làm cho tất cả các lớp serializable và hai, một số các lớp học có tài liệu tham khảo để điều khiển! Và điều khiển không thể tuần tự hóa được. Vì vậy, cần chính việc tái cấu trúc. Vì vậy ... vì chúng tôi muốn vượt qua đối tượng của bất kỳ lớp nào để kéo từ/đến WPF trong cùng một ứng dụng, tôi quyết định tạo lớp bao bọc, với thuộc tính Serializable và triển khai ISerializable. Tôi sẽ có 1 contructor với 1 tham số của loại "đối tượng" mà sẽ là dữ liệu kéo thực tế. Trình bao bọc đó, khi serializing/de-serializing, sẽ tuần tự hóa không phải chính đối tượng ... mà đúng hơn là IntPtr tới đối tượng (mà chúng ta có thể làm vì chúng ta chỉ muốn functionnality đó trong ứng dụng duy nhất 1 instance của chúng ta.) Xem mẫu mã bên dưới:

[Serializable] 
public class DataContainer : ISerializable 
{ 
public object Data { get; set; } 

public DataContainer(object data) 
{ 
    Data = data; 
} 

// Deserialization constructor 
protected DataContainer(SerializationInfo info, StreamingContext context) 
{ 
    IntPtr address = (IntPtr)info.GetValue("dataAddress", typeof(IntPtr)); 
    GCHandle handle = GCHandle.FromIntPtr(address); 
    Data = handle.Target; 
    handle.Free(); 
} 

#region ISerializable Members 

public void GetObjectData(SerializationInfo info, StreamingContext context) 
{ 
    GCHandle handle = GCHandle.Alloc(Data); 
    IntPtr address = GCHandle.ToIntPtr(handle); 
    info.AddValue("dataAddress", address); 
} 

#endregion 
} 

Để giữ cho functionnality IDataObject, tôi tạo ra các wrapper DataObject sau:

public class DataObject : IDataObject 
{ 
System.Collections.Hashtable _Data = new System.Collections.Hashtable(); 

public DataObject() { } 

public DataObject(object data) 
{ 
    SetData(data); 
} 

public DataObject(string format, object data) 
{ 
    SetData(format, data); 
} 

#region IDataObject Members 

public object GetData(Type format) 
{ 
    return _Data[format.FullName]; 
} 

public bool GetDataPresent(Type format) 
{ 
    return _Data.ContainsKey(format.FullName); 
} 

public string[] GetFormats() 
{ 
    string[] strArray = new string[_Data.Keys.Count]; 
    _Data.Keys.CopyTo(strArray, 0); 
    return strArray; 
} 

public string[] GetFormats(bool autoConvert) 
{ 
    return GetFormats(); 
} 

private void SetData(object data, string format) 
{ 
    object obj = new DataContainer(data); 

    if (string.IsNullOrEmpty(format)) 
    { 
     // Create a dummy DataObject object to retrieve all possible formats. 
     // Ex.: For a System.String type, GetFormats returns 3 formats: 
     // "System.String", "UnicodeText" and "Text" 
     System.Windows.Forms.DataObject dataObject = new System.Windows.Forms.DataObject(data); 
     foreach (string fmt in dataObject.GetFormats()) 
     { 
      _Data[fmt] = obj; 
     } 
    } 
    else 
    { 
     _Data[format] = obj; 
    } 
} 

public void SetData(object data) 
{ 
    SetData(data, null); 
} 

#endregion 
} 

Và chúng tôi đang sử dụng các lớp trên như thế này:

myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject)); 

// in the drop event for example 
e.Data.GetData(typeof(myNonSerializableClass)); 

Tôi biết tôi biết ... nó không phải là rất khá ... nhưng nó đang làm những gì chúng tôi muốn. Chúng tôi cũng đã tạo ra một lớp helper DragDrop mà mặt nạ tạo DataObject và đã templated chức năng GetData để lấy dữ liệu mà không cần bất kỳ diễn viên ... một chút như:

myNonSerializableClass newObj = DragDropHelper.GetData<myNonSerializableClass>(e.Data); 

Vì vậy, nhờ một lần nữa cho trả lời! Các bạn đã cho tôi những ý tưởng hay để xem xét các giải pháp khả thi!

-Oli

+0

Cảm ơn đã gửi bài này, nó đã giúp tôi giải quyết một vấn đề tương tự trong mã của tôi. Tôi đã thêm [Serializable] để thực hiện IDataObject của tôi và đột nhiên E_FAIL: s bí ẩn đã chấm dứt. –

0

Có lẽ sự kiện là theo cách ngược lại. PreviewDragEnter nên liên quan với WPFTextBox. Ngoài ra, hãy xem lớp DragEventArgs. Có một trong System.Windows.Form (phiên bản Windows Form) và một trong System.Windows (cho phiên bản WPF).

5

Tôi đã gặp sự cố "tương tự" một thời gian trước đây vì vậy ít nhất tôi có thể cho bạn biết những gì tôi đã phát hiện ra.

Dường như .Net đang sử dụng chức năng truy cập từ xa OLE khi thao tác kéo/thả được thực hiện trong nhưng trường hợp đơn giản nhất. Đối với một số lý do GetDataPresent sẽ trong những trường hợp này thành công và GetData sẽ thất bại. Điều này càng được thêm vào bởi một thực tế là có một vài phiên bản của IDataObject trong khung .Net.

Windows Forms mặc định là System.Windows.Forms.IDataObject. Tuy nhiên, trong trường hợp của bạn, bạn có thể thử cung cấp cho System.Runtime.InteropServices.ComTypes.IDataObject một shot thay thế. Bạn cũng có thể xem cuộc thảo luận của tôi here.

Hy vọng điều này sẽ hữu ích.

2

Dường như thật ngạc nhiên ngay từ cái nhìn đầu tiên. Tôi đã thử nó nhưng có một số lỗi về triển khai. Tôi bắt đầu sửa một số lỗi khi quyết định tìm thứ gì đó đơn giản hơn một chút, không có con trỏ (humm Tôi không thích điều đó, đặc biệt là với bộ sưu tập rác, nhưng tôi không biết liệu nó có tác động thực sự không) và điều đó không sử dụng Interop.

Tôi nghĩ ra điều đó. Nó làm việc cho tôi và tôi hy vọng nó sẽ làm việc cho bất cứ ai khác. Nó chỉ được dùng để kéo thả cục bộ (bên trong cùng một ứng dụng).

Làm thế nào để sử dụng để kéo:

DragDrop.DoDragDrop(listBoxOfAvailableScopes, new DragDropLocal(GetSelectedSimulResultScopes()), 
               DragDropEffects.Copy); 

Làm thế nào để sử dụng để thả (get):

DragDropLocal dragDropLocal = (DragDropLocal)e.Data.GetData(typeof(DragDropLocal)); 
      SimulResultScopes simulResultScopes = (SimulResultScopes)dragDropLocal.GetObject(); 

Code:

namespace Util 
{ 
    [Serializable] 
    public class DragDropLocal 
    { 
     private static readonly Dictionary<Guid, object> _dictOfDragDropLocalKeyToDragDropSource = new Dictionary<Guid, object>(); 

     private Guid _guid = Guid.NewGuid(); 

     public DragDropLocal(object objToDrag) 
     { 
      _dictOfDragDropLocalKeyToDragDropSource.Add(_guid, objToDrag); 
     } 

     public object GetObject() 
     { 
      object obj; 
      _dictOfDragDropLocalKeyToDragDropSource.TryGetValue(_guid, out obj); 
      return obj; 
     } 

     ~DragDropLocal() 
     { 
      _dictOfDragDropLocalKeyToDragDropSource.Remove(_guid); 
     } 
    } 
} 
Các vấn đề liên quan