Подтвердить что ты не робот

WinForms Interop, Drag & Drop из WinForms → WPF

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

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

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

Спасибо!


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

Случай 1) работает отлично, но случай 2) сбой при попытке получить данные о переходе с помощью GetData(). GetDataPresent ( "WindowsFormsApplication1.MyContainerClass" ) возвращает "true", поэтому теоретически я должен иметь возможность извлекать мои данные о выбросах такого типа, как в 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;
    }
}
4b9b3361

Ответ 1

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

После нескольких экспериментов, испытаний и ошибок и немного "Reflector'ing" мне удалось выяснить, почему именно я получил сообщение об ошибке: "Ошибка HRESULT E_FAIL была возвращена из вызова COM компонент".

Это было связано с тем, что при перетаскивании данных WPF ↔ Winforms в одном приложении данные должны быть Serializable!

Я проверил, насколько сложно было бы преобразовать все наши классы в "Serializable", и я бы по-настоящему пострадал по нескольким причинам... один, нам нужно было бы практически сделать все классы сериализуемые и два, некоторые из этих классов имеют ссылки на элементы управления! И элементы управления не могут быть сериализованы. Поэтому понадобился бы большой рефакторинг.

Итак... поскольку мы хотели передать любой объект любого класса для перетаскивания из/в WPF внутри одного и того же приложения, я решил создать класс-оболочку с атрибутом Serializable и реализовать ISerializable. У меня было бы 1 contructor с 1 параметром типа "объект", который был бы фактическим данными перетаскивания. Эта оболочка при сериализации/де-сериализации сериализует не сам объект... а скорее 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));

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

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

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

-Oli

Ответ 2

У меня была "аналогичная" проблема некоторое время назад, поэтому я могу хотя бы сказать вам, что я узнал.

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

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

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

Ответ 3

Кажется замечательным с первого взгляда. Я попробовал, но получил некоторые ошибки в реализации. Я начал исправлять некоторые ошибки, когда я решил искать что-то немного более упрощенное, у которого нет указателей (humm мне это не нравится, особенно с сборкой мусора, но я понятия не имею, может ли это иметь реальное воздействие) и которые не используют 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);
        }
    }
}

Ответ 4

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