Browse Source

Initial Drag+Drop support for windows

pull/1417/head
boombuler 8 years ago
parent
commit
1647d95aa6
  1. 80
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  2. 71
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  3. 47
      src/Windows/Avalonia.Win32/OleContext.cs
  4. 141
      src/Windows/Avalonia.Win32/OleDataObject.cs
  5. 131
      src/Windows/Avalonia.Win32/OleDropTarget.cs
  6. 10
      src/Windows/Avalonia.Win32/WindowImpl.cs

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

71
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
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
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
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
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;

Loading…
Cancel
Save