【问题标题】:WinForms Interop, Drag & Drop from WinForms -> WPFWinForms 互操作,从 WinForms 拖放 -> WPF
【发布时间】:2010-11-15 19:58:05
【问题描述】:

我正在尝试将数据从应用程序的 Winforms 部分拖到包含在“ElementHost”中的 WPF 控件上。当我尝试这样做时它会崩溃。

尝试同样的事情,但从 Winforms 到 Winforms 工作正常。 (见下面的示例代码)

我需要帮助来完成这项工作...有任何线索我做错了什么吗?

谢谢!


示例:
在下面的示例代码中,我只是尝试拖动在 1) System.Windows.Forms.TextBox (Winforms) 和 2) System.Windows.TextBox (WPF) 上的标签控件上启动拖动时创建的自定义 MyContainerClass 对象, 添加到 ElementHost)。

案例 1) 工作正常,但案例 2) 在尝试使用 GetData() 检索放置数据时崩溃。 GetDataPresent("WindowsFormsApplication1.MyContainerClass") 返回“true”,所以理论上,我应该能够像在 Winforms 中那样检索该类型的放置数据。

这是崩溃的堆栈跟踪:

“对 COM 组件的调用已返回错误 HRESULT E_FAIL”,并带有以下堆栈跟踪: 在 System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 错误代码,IntPtr 错误信息) 在 System.Windows.Forms.DataObject.GetDataIntoOleStructs(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetDataHere(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.Forms.DataObject.System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC& 格式等,STGMEDIUM& 介质) 在 System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& 格式等,STGMEDIUM& 中) 在 System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(字符串格式,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(字符串格式,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetData(字符串格式,布尔自动转换,DVASPECT 方面,Int32 索引) 在 System.Windows.DataObject.OleConverter.GetData(字符串格式,布尔自动转换) 在 System.Windows.DataObject.GetData(字符串格式,布尔自动转换) 在 System.Windows.DataObject.GetData(字符串格式) 在 WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 48 中的 WindowsFormsApplication1.Form1.textBox_PreviewDragEnter(Object sender, DragEventArgs e)

这里有一些代码:

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

【问题讨论】:

    标签: wpf winforms interop drag-and-drop


    【解决方案1】:

    @Pedery & jmayor:谢谢大家的建议! (见下面我的发现)

    经过相当多的实验、试验和错误,以及一些“反射”,我设法弄清楚为什么我会收到神秘的错误消息“错误 HRESULT E_FAIL 已从对 COM 的调用中返回组件”。

    这是因为当在同一个应用程序中拖动数据WPF Winforms时,该数据必须可序列化!

    我已经检查了将我们所有的类转换为“可序列化”有多么困难,出于几个原因,我会感到非常痛苦......第一,我们实际上需要让所有类可序列化和两个,其中一些类引用了控件!并且控件不可序列化。因此需要进行一次重大重构。

    所以...因为我们想要传递任何类的 any 对象以在同一应用程序中从 WPF 拖动/拖动到 WPF,所以我决定创建一个包装类,具有 Serializable 属性并实现 ISerializable .我将有 1 个带有 1 个“对象”类型参数的构造函数,这将是实际的拖动数据。该包装器在序列化/反序列化时不会序列化对象本身......而是将 IntPtr 序列化到对象(我们可以这样做,因为我们只希望在我们的 1 个实例应用程序中实现该功能。)请参见下面的代码示例:

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

    为了保持 IDataObject 功能,我创建了以下 DataObject 包装器:

    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
    }
    

    我们使用上面的类是这样的:

    myControl.DoDragDrop(new MyNamespace.DataObject(myNonSerializableObject));
    
    // in the drop event for example
    e.Data.GetData(typeof(myNonSerializableClass));
    

    我知道我知道......这不是很漂亮......但它正在做我们想要的。我们还创建了一个拖放帮助器类,它掩盖了 DataObject 的创建,并具有模板化的 GetData 函数来检索数据而无需任何强制转换......有点像:

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

    再次感谢您的回复!你们给了我很好的想法,在哪里寻找可能的解决方案!

    -奥利

    【讨论】:

    • 感谢您发布此内容,它帮助我解决了代码中的类似问题。我已将 [Serializable] 添加到我的 IDataObject 实现中,突然神秘的 E_FAIL:s 停止了。
    【解决方案2】:

    前段时间我遇到了一个“类似”的问题,所以我至少可以告诉你我发现了什么。

    似乎 .Net 在执行拖放操作时诉诸 OLE 远程处理,但这是最简单的情况。由于某种原因,GetDataPresent 在这些情况下会成功,而 GetData 会失败。 .Net 框架中有多个版本的 IDataObject,这进一步使这一点变得神秘。

    Windows 窗体默认为 System.Windows.Forms.IDataObject。但是,在您的情况下,您可以尝试给 System.Runtime.InteropServices.ComTypes.IDataObject 一个镜头。你也可以看看我的讨论here

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      乍一看似乎很棒。我试过了,但在实现上遇到了一些错误。 当我决定寻找更简单的东西时,我开始纠正一些错误,没有指针(嗯,我不喜欢这样,特别是在收集碳水化合物时,但我不知道它是否会产生真正的影响)并且不使用互操作。

      我想出了这个。它适用于我,我希望它适用于其他任何人。它仅用于本地拖放(在同一应用内)。

      如何使用拖拽:

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

      如何使用drop(get):

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

      代码:

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

      【讨论】:

        【解决方案4】:

        也许事情正好相反。 PreviewDragEnter 应该与 WPFTextBox 相关。还要注意 DragEventArgs 类。 System.Windows.Form(Windows 窗体版本)中有一个,System.Windows 下有一个(WPF 版本)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-08-06
          • 1970-01-01
          • 2010-10-20
          • 1970-01-01
          • 1970-01-01
          • 2023-03-04
          • 1970-01-01
          相关资源
          最近更新 更多