WinForms Interop, перетаскивание из WinForms -> WPF - PullRequest
17 голосов
/ 31 июля 2009

Я пытаюсь перетащить данные из части 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;
    }
}

Ответы [ 4 ]

16 голосов
/ 17 августа 2009

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

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

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

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

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

5 голосов
/ 07 августа 2009

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

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

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

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

2 голосов
/ 27 января 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...