Procházet zdrojové kódy

reworked the dataobject for drag+drop

boombuler před 7 roky
rodič
revize
be68b32440

+ 2 - 0
src/Avalonia.Controls/DragDrop/IDataObject.cs

@@ -11,5 +11,7 @@ namespace Avalonia.Controls.DragDrop
         string GetText();
 
         IEnumerable<string> GetFileNames();
+        
+        object Get(string dataFormat);
     }
 }

+ 11 - 2
src/OSX/Avalonia.MonoMac/DraggingInfo.cs

@@ -15,8 +15,7 @@ namespace Avalonia.MonoMac
         {
             _info = info;
         }
-
-
+        
         internal static NSDragOperation ConvertDragOperation(DragDropEffects d)
         {
             NSDragOperation result = NSDragOperation.None;
@@ -77,5 +76,15 @@ namespace Avalonia.MonoMac
         {
             return GetDataFormats().Any(f => f == dataFormat);
         }
+
+        public object Get(string dataFormat)
+        {
+            if (dataFormat == DataFormats.Text)
+                return GetText();
+            if (dataFormat == DataFormats.FileNames)
+                return GetFileNames();
+
+            return _info.DraggingPasteboard.GetDataForType(dataFormat).ToArray();
+        }
     }
 }

+ 10 - 10
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@@ -10,34 +10,34 @@ namespace Avalonia.Win32
 {   
     static class ClipboardFormats
     {
+        private const int MAX_FORMAT_NAME_LENGTH = 260;
+
         class ClipboardFormat
         {
             public short Format { get; private set; }
             public string Name { get; private set; }
+            public short[] Synthesized { get; private set; }
 
-            public ClipboardFormat(string name, short format)
+            public ClipboardFormat(string name, short format, params short[] synthesized)
             {
                 Format = format;
                 Name = name;
+                Synthesized = synthesized;
             }
         }
 
         private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>()
         {
-            new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT),
+            new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT, (short)UnmanagedMethods.ClipboardFormat.CF_TEXT),
             new ClipboardFormat(DataFormats.FileNames, (short)UnmanagedMethods.ClipboardFormat.CF_HDROP),
         };
 
 
         private static string QueryFormatName(short format)
         {
-            int len = UnmanagedMethods.GetClipboardFormatName(format, null, 0);
-            if (len > 0)
-            {
-                StringBuilder sb = new StringBuilder(len);
-                if (UnmanagedMethods.GetClipboardFormatName(format, sb, len) <= len)
-                    return sb.ToString();
-            }
+            StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH);
+            if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0)
+                return sb.ToString();
             return null;
         }
 
@@ -45,7 +45,7 @@ namespace Avalonia.Win32
         {
             lock (FormatList)
             {
-                var pd = FormatList.FirstOrDefault(f => f.Format == format);
+                var pd = FormatList.FirstOrDefault(f => f.Format == format || Array.IndexOf(f.Synthesized, format) >= 0);
                 if (pd == null)
                 {
                     string name = QueryFormatName(format);

+ 312 - 0
src/Windows/Avalonia.Win32/DataObject.cs

@@ -0,0 +1,312 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+using Avalonia.Controls.DragDrop;
+using Avalonia.Win32.Interop;
+using IDataObject = Avalonia.Controls.DragDrop.IDataObject;
+using IOleDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
+
+namespace Avalonia.Win32
+{
+    class DataObject : IDataObject, IOleDataObject
+    {
+        class FormatEnumerator : IEnumFORMATETC
+        {
+            private FORMATETC[] _formats;
+            private int _current;
+
+            private FormatEnumerator(FORMATETC[] formats, int current)
+            {
+                _formats = formats;
+                _current = current;
+            }
+
+            public FormatEnumerator(IDataObject dataobj)
+            {
+                _formats = dataobj.GetDataFormats().Select(ConvertToFormatEtc).ToArray();
+                _current = 0;
+            }
+
+            private FORMATETC ConvertToFormatEtc(string aFormatName)
+            {
+                FORMATETC result = default(FORMATETC);
+                result.cfFormat = ClipboardFormats.GetFormat(aFormatName);
+                result.dwAspect = DVASPECT.DVASPECT_CONTENT;
+                result.ptd = IntPtr.Zero;
+                result.lindex = -1;
+                result.tymed = TYMED.TYMED_HGLOBAL;
+                return result;
+            }
+
+            public void Clone(out IEnumFORMATETC newEnum)
+            {
+                newEnum = new FormatEnumerator(_formats, _current);
+            }
+
+            public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched)
+            {
+                if (rgelt == null)
+                    return unchecked((int)UnmanagedMethods.HRESULT.E_INVALIDARG);
+
+                int i = 0;
+                while (i < celt && _current < _formats.Length)
+                {
+                    rgelt[i] = _formats[_current];
+                    _current++;
+                    i++;
+                }
+                if (pceltFetched != null)
+                    pceltFetched[0] = i;
+
+                if (i != celt)
+                    return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            }
+
+            public int Reset()
+            {
+                _current = 0;
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            }
+
+            public int Skip(int celt)
+            {
+                _current += Math.Min(celt, int.MaxValue - _current);
+                if (_current >= _formats.Length)
+                    return unchecked((int)UnmanagedMethods.HRESULT.S_FALSE);
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            }
+        }
+
+        private const int DV_E_TYMED = unchecked((int)0x80040069);
+        private const int DV_E_DVASPECT = unchecked((int)0x8004006B);
+        private const int DV_E_FORMATETC = unchecked((int)0x80040064);
+        private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003);
+        private const int STG_E_MEDIUMFULL = unchecked((int)0x80030070);
+        private const int GMEM_ZEROINIT = 0x0040;
+        private const int GMEM_MOVEABLE = 0x0002;
+
+
+        IDataObject _wrapped;
+        
+        public DataObject(IDataObject wrapped)
+        {
+            _wrapped = wrapped;
+        }
+
+        #region IDataObject
+        bool IDataObject.Contains(string dataFormat)
+        {
+            return _wrapped.Contains(dataFormat);
+        }
+
+        IEnumerable<string> IDataObject.GetDataFormats()
+        {
+            return _wrapped.GetDataFormats();
+        }
+
+        IEnumerable<string> IDataObject.GetFileNames()
+        {
+            return _wrapped.GetFileNames();
+        }
+
+        string IDataObject.GetText()
+        {
+            return _wrapped.GetText();
+        }
+
+        object IDataObject.Get(string dataFormat)
+        {
+            return _wrapped.Get(dataFormat);
+        }
+        #endregion
+
+        #region IOleDataObject
+
+        int IOleDataObject.DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
+        {
+            if (_wrapped is IOleDataObject ole)
+                return ole.DAdvise(ref pFormatetc, advf, adviseSink, out connection);
+            connection = 0;
+            return OLE_E_ADVISENOTSUPPORTED;
+        }
+
+        void IOleDataObject.DUnadvise(int connection)
+        {
+            if (_wrapped is IOleDataObject ole)
+                ole.DUnadvise(connection);
+            Marshal.ThrowExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
+        }
+
+        int IOleDataObject.EnumDAdvise(out IEnumSTATDATA enumAdvise)
+        {
+            if (_wrapped is IOleDataObject ole)
+                return ole.EnumDAdvise(out enumAdvise);
+
+            enumAdvise = null;
+            return OLE_E_ADVISENOTSUPPORTED;
+        }
+
+        IEnumFORMATETC IOleDataObject.EnumFormatEtc(DATADIR direction)
+        {
+            if (_wrapped is IOleDataObject ole)
+                return ole.EnumFormatEtc(direction);
+            if (direction == DATADIR.DATADIR_GET)
+                return new FormatEnumerator(_wrapped);
+            throw new NotSupportedException();
+        }
+
+        int IOleDataObject.GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
+        {
+            if (_wrapped is IOleDataObject ole)
+                return ole.GetCanonicalFormatEtc(ref formatIn, out formatOut);
+
+            formatOut = new FORMATETC();
+            formatOut.ptd = IntPtr.Zero;
+            return unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL);
+        }
+
+        void IOleDataObject.GetData(ref FORMATETC format, out STGMEDIUM medium)
+        {
+            if (_wrapped is IOleDataObject ole)
+            {
+                ole.GetData(ref format, out medium);
+                return;
+            }
+            if(!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
+                Marshal.ThrowExceptionForHR(DV_E_TYMED);
+
+            if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
+                Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
+
+            string fmt = ClipboardFormats.GetFormat(format.cfFormat);
+            if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
+                Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
+
+            medium = default(STGMEDIUM);
+            medium.tymed = TYMED.TYMED_HGLOBAL;
+            int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
+            Marshal.ThrowExceptionForHR(result);
+        }
+
+        void IOleDataObject.GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
+        {
+            if (_wrapped is IOleDataObject ole)
+            {
+                ole.GetDataHere(ref format, ref medium);
+                return;
+            }
+
+            if (medium.tymed != TYMED.TYMED_HGLOBAL || !format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
+                Marshal.ThrowExceptionForHR(DV_E_TYMED);
+
+            if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
+                Marshal.ThrowExceptionForHR(DV_E_DVASPECT);
+
+            string fmt = ClipboardFormats.GetFormat(format.cfFormat);
+            if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt))
+                Marshal.ThrowExceptionForHR(DV_E_FORMATETC);
+
+            if (medium.unionmember == IntPtr.Zero)
+                Marshal.ThrowExceptionForHR(STG_E_MEDIUMFULL);
+
+            int result = WriteDataToHGlobal(fmt, ref medium.unionmember);
+            Marshal.ThrowExceptionForHR(result);
+        }
+
+        int IOleDataObject.QueryGetData(ref FORMATETC format)
+        {
+            if (_wrapped is IOleDataObject ole)
+                return ole.QueryGetData(ref format);
+            if (format.dwAspect != DVASPECT.DVASPECT_CONTENT)
+                return DV_E_DVASPECT;
+            if (!format.tymed.HasFlag(TYMED.TYMED_HGLOBAL))
+                return DV_E_TYMED;
+
+            string dataFormat = ClipboardFormats.GetFormat(format.cfFormat);
+            if (!string.IsNullOrEmpty(dataFormat) && _wrapped.Contains(dataFormat))
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            return DV_E_FORMATETC;
+        }
+        
+        void IOleDataObject.SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
+        {
+            if (_wrapped is IOleDataObject ole)
+            {
+                ole.SetData(ref formatIn, ref medium, release);
+                return;
+            }
+            Marshal.ThrowExceptionForHR(unchecked((int)UnmanagedMethods.HRESULT.E_NOTIMPL));
+        }
+
+        private int WriteDataToHGlobal(string dataFormat, ref IntPtr hGlobal)
+        {
+            object data = _wrapped.Get(dataFormat);
+            if (dataFormat == DataFormats.Text || data is string)
+                return WriteStringToHGlobal(ref hGlobal, Convert.ToString(data));
+            if (dataFormat == DataFormats.FileNames && data is IEnumerable<string> files)
+                return WriteFileListToHGlobal(ref hGlobal, files);
+            return DV_E_TYMED;
+        }
+
+        private int WriteFileListToHGlobal(ref IntPtr hGlobal, IEnumerable<string> files)
+        {
+            if (!files?.Any() ?? false)
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+
+            char[] filesStr = (string.Join("\0", files) + "\0\0").ToCharArray();
+            _DROPFILES df = new _DROPFILES();
+            df.pFiles = Marshal.SizeOf<_DROPFILES>();
+            df.fWide = true;
+            
+            int required = (filesStr.Length * sizeof(char)) + Marshal.SizeOf<_DROPFILES>();
+            if (hGlobal == IntPtr.Zero)
+                hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, required);
+
+            long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
+            if (required > available)
+                return STG_E_MEDIUMFULL;
+
+            IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
+            try
+            {
+                Marshal.StructureToPtr(df, ptr, false);
+
+                Marshal.Copy(filesStr, 0, ptr + Marshal.SizeOf<_DROPFILES>(), filesStr.Length);
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            }
+            finally
+            {
+                UnmanagedMethods.GlobalUnlock(hGlobal);
+            }
+        }
+
+        private int WriteStringToHGlobal(ref IntPtr hGlobal, string data)
+        {
+            int required = (data.Length + 1) * sizeof(char);
+            if (hGlobal == IntPtr.Zero)
+                hGlobal = UnmanagedMethods.GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, required);
+
+            long available = UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
+            if (required > available)
+                return STG_E_MEDIUMFULL;
+            
+            IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
+            try
+            {
+                char[] chars = (data + '\0').ToCharArray();
+                Marshal.Copy(chars, 0, ptr, chars.Length);
+                return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+            }
+            finally
+            {
+                UnmanagedMethods.GlobalUnlock(hGlobal);
+            }
+        }
+
+        #endregion
+    }
+}

+ 49 - 4
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -973,7 +973,11 @@ namespace Avalonia.Win32.Interop
         [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
         public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
 
-        
+        [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true, PreserveSig = false)]
+        public static extern void DoDragDrop(IDataObject dataObject, IDropSource dropSource, int allowedEffects, int[] finalEffect);
+
+
+
         public enum MONITOR
         {
             MONITOR_DEFAULTTONULL = 0x00000000,
@@ -1013,11 +1017,28 @@ namespace Avalonia.Win32.Interop
             MDT_DEFAULT = MDT_EFFECTIVE_DPI
         } 
 
-        public enum ClipboardFormat
+        public enum ClipboardFormat 
         {
+            /// <summary>
+            /// Text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data. Use this format for ANSI text.
+            /// </summary>
             CF_TEXT = 1,
+            /// <summary>
+            /// A handle to a bitmap
+            /// </summary>
+            CF_BITMAP = 2,
+            /// <summary>
+            /// A memory object containing a BITMAPINFO structure followed by the bitmap bits.
+            /// </summary>
+            CF_DIB = 3,
+            /// <summary>
+            /// Unicode text format. Each line ends with a carriage return/linefeed (CR-LF) combination. A null character signals the end of the data.
+            /// </summary>
             CF_UNICODETEXT = 13,
-            CF_HDROP = 15
+            /// <summary>
+            /// A handle to type HDROP that identifies a list of files. 
+            /// </summary>
+            CF_HDROP = 15,
         }
 
         public struct MSG
@@ -1160,7 +1181,9 @@ namespace Avalonia.Win32.Interop
             S_FALSE = 0x0001,
             S_OK = 0x0000,
             E_INVALIDARG = 0x80070057,
-            E_OUTOFMEMORY = 0x8007000E
+            E_OUTOFMEMORY = 0x8007000E,
+            E_NOTIMPL = 0x80004001,
+            E_UNEXPECTED = 0x8000FFFF,
         }
 
         public enum Icons
@@ -1351,4 +1374,26 @@ namespace Avalonia.Win32.Interop
         [PreserveSig]
         UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
     }
+
+    [ComImport]
+    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    [Guid("00000121-0000-0000-C000-000000000046")]
+    internal interface IDropSource
+    {
+        [PreserveSig]
+        int QueryContinueDrag(int fEscapePressed, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState);
+        [PreserveSig]
+        int GiveFeedback([MarshalAs(UnmanagedType.U4)] [In] int dwEffect);
+    }
+
+
+    [StructLayoutAttribute(LayoutKind.Sequential)]
+    internal struct _DROPFILES
+    {
+        public Int32 pFiles;
+        public Int32 X;
+        public Int32 Y;
+        public bool fNC;
+        public bool fWide;
+    }
 }

+ 5 - 0
src/Windows/Avalonia.Win32/OleDataObject.cs

@@ -39,6 +39,11 @@ namespace Avalonia.Win32
             return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable<string>;
         }
 
+        public object Get(string dataFormat)
+        {
+            return GetDataFromOleHGLOBAL(dataFormat, DVASPECT.DVASPECT_CONTENT);
+        }
+
         private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect)
         {
             FORMATETC formatEtc = new FORMATETC();

+ 41 - 0
src/Windows/Avalonia.Win32/OleDragSource.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    class OleDragSource : IDropSource
+    {
+        private const int DRAGDROP_S_USEDEFAULTCURSORS = 0x00040102;
+        private const int DRAGDROP_S_DROP = 0x00040100;
+        private const int DRAGDROP_S_CANCEL = 0x00040101;
+
+        private const int KEYSTATE_LEFTMB = 1;
+        private const int KEYSTATE_MIDDLEMB = 16;
+        private const int KEYSTATE_RIGHTMB = 2;
+        private static readonly int[] MOUSE_BUTTONS = new int[] { KEYSTATE_LEFTMB, KEYSTATE_MIDDLEMB, KEYSTATE_RIGHTMB };
+
+        public int QueryContinueDrag(int fEscapePressed, int grfKeyState)
+        {
+            if (fEscapePressed != 0)
+                return DRAGDROP_S_CANCEL;
+
+            int pressedMouseButtons = MOUSE_BUTTONS.Where(mb => (grfKeyState & mb) == mb).Count();
+
+            if (pressedMouseButtons >= 2)
+                return DRAGDROP_S_CANCEL;
+            if (pressedMouseButtons == 0)
+                return DRAGDROP_S_DROP;
+
+            return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+        }
+
+        public int GiveFeedback(int dwEffect)
+        {
+            if (dwEffect != 0)
+                return DRAGDROP_S_USEDEFAULTCURSORS;
+            return unchecked((int)UnmanagedMethods.HRESULT.S_OK);
+        }
+    }
+}

+ 7 - 4
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -55,8 +55,9 @@ namespace Avalonia.Win32
                 pdwEffect = DropEffect.None;
                 return UnmanagedMethods.HRESULT.S_OK;
             }
-            
-            _currentDrag = new OleDataObject(pDataObj);
+            _currentDrag = pDataObj as IDataObject;
+            if (_currentDrag == null)
+                _currentDrag = new OleDataObject(pDataObj);
             var args = new RawDragEvent(
                 _dragDevice,
                 RawDragEventType.DragEnter, 
@@ -124,8 +125,10 @@ namespace Avalonia.Win32
                     pdwEffect = DropEffect.None;
                     return UnmanagedMethods.HRESULT.S_OK;
                 }
-            
-                _currentDrag= new OleDataObject(pDataObj);
+
+                _currentDrag = pDataObj as IDataObject;
+                if (_currentDrag == null)
+                    _currentDrag= new OleDataObject(pDataObj);
                 
                 var args = new RawDragEvent(
                     _dragDevice,