Kaynağa Gözat

Initial Drag+Drop support for windows

boombuler 7 yıl önce
ebeveyn
işleme
1647d95aa6

+ 80 - 0
src/Windows/Avalonia.Win32/ClipboardFormats.cs

@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using Avalonia.Controls.DragDrop;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{   
+    static class ClipboardFormats
+    {
+        class ClipboardFormat
+        {
+            public short Format { get; private set; }
+            public string Name { get; private set; }
+
+            public ClipboardFormat(string name, short format)
+            {
+                Format = format;
+                Name = name;
+            }
+        }
+
+        private static readonly List<ClipboardFormat> FormatList = new List<ClipboardFormat>()
+        {
+            new ClipboardFormat(DataFormats.Text, (short)UnmanagedMethods.ClipboardFormat.CF_UNICODETEXT),
+            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();
+            }
+            return null;
+        }
+
+        public static string GetFormat(short format)
+        {
+            lock (FormatList)
+            {
+                var pd = FormatList.FirstOrDefault(f => f.Format == format);
+                if (pd == null)
+                {
+                    string name = QueryFormatName(format);
+                    if (string.IsNullOrEmpty(name))
+                        name = string.Format("Unknown_Format_{0}", format);
+                    pd = new ClipboardFormat(name, format);
+                    FormatList.Add(pd);
+                }
+                return pd.Name;
+            }
+        }
+
+        public static short GetFormat(string format)
+        {
+            lock (FormatList)
+            {
+                var pd = FormatList.FirstOrDefault(f => StringComparer.OrdinalIgnoreCase.Equals(f.Name, format));
+                if (pd == null)
+                {
+                    int id = UnmanagedMethods.RegisterClipboardFormat(format);
+                    if (id == 0)
+                        throw new Win32Exception();
+                    pd = new ClipboardFormat(format, (short)id);
+                    FormatList.Add(pd);
+                }
+                return pd.Format;
+            }
+        }
+        
+
+    }
+}

+ 70 - 1
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -5,6 +5,7 @@ using System;
 using System.Diagnostics.CodeAnalysis;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
 using System.Text;
 
 // ReSharper disable InconsistentNaming
@@ -951,6 +952,28 @@ namespace Avalonia.Win32.Interop
         [DllImport("msvcrt.dll", EntryPoint="memcpy", SetLastError = false, CallingConvention=CallingConvention.Cdecl)]
         public static extern IntPtr CopyMemory(IntPtr dest, IntPtr src, UIntPtr count); 
         
+        [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
+        public static extern HRESULT RegisterDragDrop(IntPtr hwnd, IDropTarget target);
+        
+        [DllImport("ole32.dll", EntryPoint = "OleInitialize")]
+        public static extern HRESULT OleInitialize(IntPtr val);
+
+        [DllImport("ole32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
+        internal static extern void ReleaseStgMedium(ref STGMEDIUM medium);
+
+        [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
+        public static extern int GetClipboardFormatName(int format, StringBuilder lpString, int cchMax);
+
+        [DllImport("user32.dll", BestFitMapping = false, CharSet = CharSet.Auto, SetLastError = true)]
+        public static extern int RegisterClipboardFormat(string format);
+
+        [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
+        public static extern IntPtr GlobalSize(IntPtr hGlobal);
+
+        [DllImport("shell32.dll", BestFitMapping = false, CharSet = CharSet.Auto)]
+        public static extern int DragQueryFile(IntPtr hDrop, int iFile, StringBuilder lpszFile, int cch);
+
+        
         public enum MONITOR
         {
             MONITOR_DEFAULTTONULL = 0x00000000,
@@ -993,7 +1016,8 @@ namespace Avalonia.Win32.Interop
         public enum ClipboardFormat
         {
             CF_TEXT = 1,
-            CF_UNICODETEXT = 13
+            CF_UNICODETEXT = 13,
+            CF_HDROP = 15
         }
 
         public struct MSG
@@ -1300,4 +1324,49 @@ namespace Avalonia.Win32.Interop
         uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder);
         
     }
+    
+    [Flags]
+    internal enum DropEffect : int
+    {
+        None = 0,
+        Copy = 1,
+        Move = 2,
+        Link = 4,
+        Scroll = -2147483648,
+    }
+    
+    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    [Guid("0000010E-0000-0000-C000-000000000046")]
+    [ComImport]
+    internal interface IOleDataObject
+    {
+        void GetData([In] ref FORMATETC format, out STGMEDIUM medium);
+        void GetDataHere([In] ref FORMATETC format, ref STGMEDIUM medium);
+        [PreserveSig]
+        int QueryGetData([In] ref FORMATETC format);
+        [PreserveSig]
+        int GetCanonicalFormatEtc([In] ref FORMATETC formatIn, out FORMATETC formatOut);
+        void SetData([In] ref FORMATETC formatIn, [In] ref STGMEDIUM medium, [MarshalAs(UnmanagedType.Bool)] bool release);
+        IEnumFORMATETC EnumFormatEtc(DATADIR direction);
+        [PreserveSig]
+        int DAdvise([In] ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection);
+        void DUnadvise(int connection);
+        [PreserveSig]
+        int EnumDAdvise(out IEnumSTATDATA enumAdvise);
+    }
+    
+    [ComImport]
+    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+    [Guid("00000122-0000-0000-C000-000000000046")]
+    internal interface IDropTarget
+    {
+        [PreserveSig]
+        UnmanagedMethods.HRESULT DragEnter([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
+        [PreserveSig]
+        UnmanagedMethods.HRESULT DragOver([MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
+        [PreserveSig]
+        UnmanagedMethods.HRESULT DragLeave();
+        [PreserveSig]
+        UnmanagedMethods.HRESULT Drop([MarshalAs(UnmanagedType.Interface)] [In] IOleDataObject pDataObj, [MarshalAs(UnmanagedType.U4)] [In] int grfKeyState, [MarshalAs(UnmanagedType.U8)] [In] long pt, [In] [Out] ref DropEffect pdwEffect);
+    }
 }

+ 47 - 0
src/Windows/Avalonia.Win32/OleContext.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Threading;
+using Avalonia.Platform;
+using Avalonia.Threading;
+using Avalonia.Win32.Interop;
+
+namespace Zippr.UIServices.Avalonia.Windows
+{
+    class OleContext
+    {
+        private static OleContext fCurrent;
+
+        internal static OleContext Current
+        {
+            get
+            {
+                if (!IsValidOleThread())
+                    return null;
+
+                if (fCurrent == null)
+                    fCurrent = new OleContext();
+                return fCurrent;
+            }
+        }
+
+
+        private OleContext()
+        {
+            if (UnmanagedMethods.OleInitialize(IntPtr.Zero) != UnmanagedMethods.HRESULT.S_OK)
+                throw new SystemException("Failed to initialize OLE");
+        }
+
+        private static bool IsValidOleThread()
+        {
+            return Dispatcher.UIThread.CheckAccess() &&
+                   Thread.CurrentThread.GetApartmentState() == ApartmentState.STA;
+        }
+
+        internal bool RegisterDragDrop(IPlatformHandle hwnd, IDropTarget target)
+        {
+            if (hwnd?.HandleDescriptor != "HWND" || target == null)
+                return false;
+
+            return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, target) == UnmanagedMethods.HRESULT.S_OK;
+        }
+    }
+}

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

@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.ComTypes;
+using System.Text;
+using Avalonia.Controls.DragDrop;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    class OleDataObject : IDragData
+    {
+        private IOleDataObject _wrapped;
+
+        public OleDataObject(IOleDataObject wrapped)
+        {
+            _wrapped = wrapped;
+        }
+
+        public bool Contains(string dataFormat)
+        {
+            return GetDataFormatsCore().Any(df => StringComparer.OrdinalIgnoreCase.Equals(df, dataFormat));
+        }
+
+        public IEnumerable<string> GetDataFormats()
+        {
+            return GetDataFormatsCore().Distinct();
+        }
+
+        public string GetText()
+        {
+            return GetDataFromOleHGLOBAL(DataFormats.Text, DVASPECT.DVASPECT_CONTENT) as string;
+        }
+
+        public IEnumerable<string> GetFileNames()
+        {
+            return GetDataFromOleHGLOBAL(DataFormats.FileNames, DVASPECT.DVASPECT_CONTENT) as IEnumerable<string>;
+        }
+
+        private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect)
+        {
+            FORMATETC formatEtc = new FORMATETC();
+            formatEtc.cfFormat = ClipboardFormats.GetFormat(format);
+            formatEtc.dwAspect = aspect;
+            formatEtc.lindex = -1;
+            formatEtc.tymed = TYMED.TYMED_HGLOBAL;
+            if (_wrapped.QueryGetData(ref formatEtc) == 0)
+            {
+                _wrapped.GetData(ref formatEtc, out STGMEDIUM medium);
+                try
+                {
+                    if (medium.unionmember != IntPtr.Zero && medium.tymed == TYMED.TYMED_HGLOBAL)
+                    {
+                        if (format == DataFormats.Text)
+                            return ReadStringFromHGlobal(medium.unionmember);
+                        if (format == DataFormats.FileNames)
+                            return ReadFileNamesFromHGlobal(medium.unionmember);
+                        return ReadBytesFromHGlobal(medium.unionmember);
+                    }
+                }
+                finally
+                {
+                    UnmanagedMethods.ReleaseStgMedium(ref medium);
+                }
+            }
+            return null;
+        }
+
+        private static IEnumerable<string> ReadFileNamesFromHGlobal(IntPtr hGlobal)
+        {
+            List<string> files = new List<string>();
+            int fileCount = UnmanagedMethods.DragQueryFile(hGlobal, -1, null, 0);
+            if (fileCount > 0)
+            {
+                for (int i = 0; i < fileCount; i++)
+                {
+                    int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0);
+                    StringBuilder sb = new StringBuilder(pathLen+1);
+
+                    if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen)
+                    {
+                        files.Add(sb.ToString());
+                    }
+                }
+            }
+            return files;
+        }
+
+        private static string ReadStringFromHGlobal(IntPtr hGlobal)
+        {
+            IntPtr ptr = UnmanagedMethods.GlobalLock(hGlobal);
+            try
+            {
+                return Marshal.PtrToStringAuto(ptr);
+            }
+            finally
+            {
+                UnmanagedMethods.GlobalUnlock(hGlobal);
+            }
+        }
+
+        private static byte[] ReadBytesFromHGlobal(IntPtr hGlobal)
+        {
+            IntPtr source = UnmanagedMethods.GlobalLock(hGlobal);
+            try
+            {
+                int size = (int)UnmanagedMethods.GlobalSize(hGlobal).ToInt64();
+                byte[] data = new byte[size];
+                Marshal.Copy(source, data, 0, size);
+                return data;
+            }
+            finally
+            {
+                UnmanagedMethods.GlobalUnlock(hGlobal);
+            }
+        }
+
+        private IEnumerable<string> GetDataFormatsCore()
+        {
+            var enumFormat = _wrapped.EnumFormatEtc(DATADIR.DATADIR_GET);
+            if (enumFormat != null)
+            {
+                enumFormat.Reset();
+                FORMATETC[] formats = new FORMATETC[1];
+                int[] fetched = { 1 };
+                while (fetched[0] > 0)
+                {
+                    fetched[0] = 0;
+                    if (enumFormat.Next(1, formats, fetched) == 0 && fetched[0] > 0)
+                    {
+                        if (formats[0].ptd != IntPtr.Zero)
+                            Marshal.FreeCoTaskMem(formats[0].ptd);
+                        
+                        yield return ClipboardFormats.GetFormat(formats[0].cfFormat);
+                    }
+                }
+            }
+        }
+    }
+}

+ 131 - 0
src/Windows/Avalonia.Win32/OleDropTarget.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Runtime.InteropServices.ComTypes;
+using Avalonia.Controls;
+using Avalonia.Controls.DragDrop;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+using Avalonia.Win32.Interop;
+
+namespace Avalonia.Win32
+{
+    class OleDropTarget : IDropTarget
+    {
+        private readonly IDragDispatcher _dragDispatcher;
+        private readonly IInputElement _target;
+        
+        private IDragData _currentDrag = null;
+
+        public OleDropTarget(IInputElement target)
+        {
+            _dragDispatcher = AvaloniaLocator.Current.GetService<IDragDispatcher>();
+            _target = target;
+        }
+
+        static DropEffect ConvertDropEffect(DragOperation operation)
+        {
+            DropEffect result = DropEffect.None;
+            if (operation.HasFlag(DragOperation.Copy))
+                result |= DropEffect.Copy;
+            if (operation.HasFlag(DragOperation.Move))
+                result |= DropEffect.Move;
+            if (operation.HasFlag(DragOperation.Link))
+                result |= DropEffect.Link;
+            return result;
+        }
+
+        static DragOperation ConvertDropEffect(DropEffect effect)
+        {
+            DragOperation result = DragOperation.None;
+            if (effect.HasFlag(DropEffect.Copy))
+                result |= DragOperation.Copy;
+            if (effect.HasFlag(DropEffect.Move))
+                result |= DragOperation.Move;
+            if (effect.HasFlag(DropEffect.Link))
+                result |= DragOperation.Link;
+            return result;
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.DragEnter(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
+        {
+            if (_dragDispatcher == null)
+            {
+                pdwEffect = DropEffect.None;
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            
+            _currentDrag = new OleDataObject(pDataObj);
+            var dragLocation = GetDragLocation(pt);
+
+            var operation = ConvertDropEffect(pdwEffect);
+            operation = _dragDispatcher.DragEnter(_target, dragLocation, _currentDrag, operation);
+            pdwEffect = ConvertDropEffect(operation);
+            
+            return UnmanagedMethods.HRESULT.S_OK;
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.DragOver(int grfKeyState, long pt, ref DropEffect pdwEffect)
+        {
+            if (_dragDispatcher == null)
+            {
+                pdwEffect = DropEffect.None;
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            
+            var dragLocation = GetDragLocation(pt);
+
+            var operation = ConvertDropEffect(pdwEffect);
+            operation = _dragDispatcher.DragOver(_target, dragLocation, _currentDrag, operation);
+            pdwEffect = ConvertDropEffect(operation);
+            
+            return UnmanagedMethods.HRESULT.S_OK;  
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.DragLeave()
+        {
+            try
+            {
+                _dragDispatcher?.DragLeave(_target);
+                return UnmanagedMethods.HRESULT.S_OK;
+            }
+            finally
+            {
+                _currentDrag = null;
+            }
+        }
+
+        UnmanagedMethods.HRESULT IDropTarget.Drop(IOleDataObject pDataObj, int grfKeyState, long pt, ref DropEffect pdwEffect)
+        {
+            try
+            {
+                if (_dragDispatcher == null)
+                {
+                    pdwEffect = DropEffect.None;
+                    return UnmanagedMethods.HRESULT.S_OK;
+                }
+            
+                _currentDrag= new OleDataObject(pDataObj);
+                var dragLocation = GetDragLocation(pt);
+
+                var operation = ConvertDropEffect(pdwEffect);
+                operation = _dragDispatcher.Drop(_target, dragLocation, _currentDrag, operation);
+                pdwEffect = ConvertDropEffect(operation);
+            
+                return UnmanagedMethods.HRESULT.S_OK;  
+            }
+            finally
+            {
+                _currentDrag = null;
+            }
+        }
+
+        private Point GetDragLocation(long dragPoint)
+        {
+            int x = (int)dragPoint;
+            int y = (int)(dragPoint >> 32);
+
+            Point screenPt = new Point(x, y);
+            return _target.PointToClient(screenPt);
+        }
+    }
+}

+ 10 - 0
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -14,6 +14,7 @@ using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Win32.Input;
 using Avalonia.Win32.Interop;
+using Zippr.UIServices.Avalonia.Windows;
 using static Avalonia.Win32.Interop.UnmanagedMethods;
 
 namespace Avalonia.Win32
@@ -34,6 +35,7 @@ namespace Avalonia.Win32
         private double _scaling = 1;
         private WindowState _showWindowState;
         private FramebufferManager _framebuffer;
+        private OleDropTarget _dropTarget;
 #if USE_MANAGED_DRAG
         private readonly ManagedWindowResizeDragHelper _managedDrag;
 #endif
@@ -308,6 +310,7 @@ namespace Avalonia.Win32
         public void SetInputRoot(IInputRoot inputRoot)
         {
             _owner = inputRoot;
+            CreateDropTarget();
         }
 
         public void SetTitle(string title)
@@ -689,6 +692,13 @@ namespace Avalonia.Win32
             }
         }
 
+        private void CreateDropTarget()
+        {
+            OleDropTarget odt = new OleDropTarget(_owner);
+            if (OleContext.Current?.RegisterDragDrop(Handle, odt) ?? false)
+                _dropTarget = odt;
+        }
+
         private Point DipFromLParam(IntPtr lParam)
         {
             return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling;