Вопрос по winforms, wpf, drag-and-drop, interop – WinForms Interop, перетаскивание из WinForms -> WPF

17

Я пытаюсь перетащить данные из части Winforms моего приложения в элементы управления WPF, которые содержатся внутри "ElementHost". И он падает, когда я пытаюсь это сделать.

Попытка сделать то же самое, но из Winforms в Winforms работает нормально. (См. Пример кода ниже)

Мне нужна помощь в создании этой работы ... есть какие-либо подсказки, что я делаю неправильно?

Спасибо!

Пример:
В приведенном ниже примере кода я просто пытаюсь перетащить пользовательский объект MyContainerClass, созданный при инициировании перетаскивания элемента управления меткой на 1) System.Windows.Forms.TextBox (Winforms) и 2) System.Windows.TextBox (WPF). , добавленный в ElementHost).

Случай 1) работает нормально, но случай 2) дает сбой при попытке извлечь удаленные данные с помощью GetData (). GetDataPresent (& quot; WindowsFormsApplication1.MyContainerClass & quot;) возвращает & quot; true & quot; Так что теоретически я должен быть в состоянии получить данные сбрасывания такого типа, как в Winforms.

Вот трассировка стека аварии:

"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  

Вот некоторый код:

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

Ваш Ответ

4   ответа
16

@Pedery & amp; jmayor: Спасибо за предложения, ребята! (см. мои выводы ниже)

После нескольких экспериментов, проб и ошибок и небольшого количества «Reflector» »мне удалось выяснить, почему именно я получал загадочное сообщение об ошибке« Ошибка HRESULT E_FAIL возвращена из вызова COM-компонента » ;.

Это было связано с тем, что при перетаскивании данных WPF & lt; - & gt; Winforms в одном приложении, эти данныеhas to быть сериализуемым!

Я проверил, насколько сложно было бы преобразовать все наши классы в «Сериализуемый». и мне было бы очень больно по нескольким причинам ... во-первых, нам нужно было бы практически сделать все классы сериализуемыми, и во-вторых, некоторые из этих классов имеют ссылки на Controls! И элементы управления не сериализуемы. Так чтоmajor рефакторинг был бы необходим.

Итак ... так как мы хотели пройтиany Объект любого класса для перетаскивания из / в WPF внутри того же приложения, я решил создать класс-оболочку с атрибутом Serializable и реализацию ISerializable. Я хотел бы иметь 1 конструктор с 1 параметром типа & quot; объект & quot; которые будут фактическими данными перетаскивания. Эта оболочка, при сериализации / десериализации, будет сериализовать не сам объект ... а скорее IntPtr к объекту (что мы можем сделать, поскольку мы хотим, чтобы эта функциональность была только в нашем приложении только с одним экземпляром). См. Пример кода ниже:

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

Я знаю, я знаю ... это не оченьpretty... но он делает то, что мы хотели. Мы также создали вспомогательный класс dragdrop, который маскирует создание DataObject и имеет шаблонные функции GetData для извлечения данных без какого-либо преобразования ... немного похоже на:

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

Так что еще раз спасибо за ответы! Вы, ребята, дали мне хорошие идеи, где искать возможные решения!

-Oli

Спасибо за публикацию, это помогло мне решить аналогичную проблему в моем коде. Я добавил [Serializable] в мою реализацию IDataObject, и неожиданно загадочные E_FAIL перестали работать.
0

события в обратном направлении. PreviewDragEnter должно быть связано с WPFTextBox. Также обратите внимание на класс DragEventArgs. Один в System.Windows.Form (версия Windows Form) и один в System.Windows (для версии WPF).

5

чтобы я мог, по крайней мере, рассказать вам, что я узнал

Кажется, .Net прибегает к удаленному удаленному взаимодействию, когда операции перетаскивания выполняются, но в простейших случаях. По какой-то причине GetDataPresent в этих случаях будет успешным, а GetData завершится ошибкой. Кроме того, это загадывается тем фактом, что в инфраструктуре .Net существует несколько версий IDataObject.

По умолчанию в Windows Forms используется System.Windows.Forms.IDataObject. Тем не менее, в вашем случае вы можете попытаться дать System.Runtime.InteropServices.ComTypes.IDataObject вместо этого выстрел. Вы также можете проверить мое обсуждениеВот.

Надеюсь это поможет.

2

но получил некоторые ошибки на реализациях. Я начал исправлять некоторые ошибки, когда решил поискать что-то попроще, без указателей (хм, мне это не нравится, особенно при сборе мусора, но я понятия не имею, может ли это оказать реальное влияние) и которые не используют Interop.

Я придумаю это. Это работает для меня, и я надеюсь, что это будет работать для всех остальных. Он предназначен только для локального перетаскивания (внутри того же приложения).

Как использовать для перетаскивания:

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

Как использовать, чтобы уронить (получить):

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

Похожие вопросы