Browse Source

reworked the dataobject for drag+drop

pull/1417/head
boombuler 8 years ago
parent
commit
be68b32440
  1. 2
      src/Avalonia.Controls/DragDrop/IDataObject.cs
  2. 13
      src/OSX/Avalonia.MonoMac/DraggingInfo.cs
  3. 20
      src/Windows/Avalonia.Win32/ClipboardFormats.cs
  4. 312
      src/Windows/Avalonia.Win32/DataObject.cs
  5. 53
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  6. 5
      src/Windows/Avalonia.Win32/OleDataObject.cs
  7. 41
      src/Windows/Avalonia.Win32/OleDragSource.cs
  8. 11
      src/Windows/Avalonia.Win32/OleDropTarget.cs

2
src/Avalonia.Controls/DragDrop/IDataObject.cs

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

13
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();
}
}
}

20
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
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
}
}

53
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
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
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);
}
}
}

11
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,

Loading…
Cancel
Save