Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Back to Shell Style Drag and Drop in .NET - Part 2
namespace DragDropLib
{
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
#region DataObject
/// <summary>
/// Implements the COM version of IDataObject including SetData.
/// </summary>
/// <remarks>
/// <para>Use this object when using shell (or other unmanged) features
/// that utilize the clipboard and/or drag and drop.</para>
/// <para>The System.Windows.DataObject (.NET 3.0) and
/// System.Windows.Forms.DataObject do not support SetData from their COM
/// IDataObject interface implementation.</para>
/// <para>To use this object with .NET drag and drop, create an instance
/// of System.Windows.DataObject (.NET 3.0) or System.Window.Forms.DataObject
/// passing an instance of DataObject as the only constructor parameter. For
/// example:</para>
/// <code>
/// System.Windows.DataObject data = new System.Windows.DataObject(new DragDropLib.DataObject());
/// </code>
/// </remarks>
[ComVisible(true)]
public class DataObject : IDataObject, IDisposable
{
#region Unmanaged functions
// These are helper functions for managing STGMEDIUM structures
[DllImport("urlmon.dll")]
private static extern int CopyStgMedium(ref STGMEDIUM pcstgmedSrc, ref STGMEDIUM pstgmedDest);
[DllImport("ole32.dll")]
private static extern void ReleaseStgMedium(ref STGMEDIUM pmedium);
#endregion // Unmanaged functions
// Our internal storage is a simple list
private IList<KeyValuePair<FORMATETC, STGMEDIUM>> storage;
/// <summary>
/// Creates an empty instance of DataObject.
/// </summary>
public DataObject()
{
storage = new List<KeyValuePair<FORMATETC, STGMEDIUM>>();
}
/// <summary>
/// Releases unmanaged resources.
/// </summary>
~DataObject()
{
Dispose(false);
}
/// <summary>
/// Clears the internal storage array.
/// </summary>
/// <remarks>
/// ClearStorage is called by the IDisposable.Dispose method implementation
/// to make sure all unmanaged references are released properly.
/// </remarks>
private void ClearStorage()
{
foreach (KeyValuePair<FORMATETC, STGMEDIUM> pair in storage)
{
STGMEDIUM medium = pair.Value;
ReleaseStgMedium(ref medium);
}
storage.Clear();
}
/// <summary>
/// Releases resources.
/// </summary>
public void Dispose()
{
Dispose(true);
}
/// <summary>
/// Releases resources.
/// </summary>
/// <param name="disposing">Indicates if the call was made by a managed caller, or the garbage collector.
/// True indicates that someone called the Dispose method directly. False indicates that the garbage collector
/// is finalizing the release of the object instance.</param>
private void Dispose(bool disposing)
{
if (disposing)
{
// No managed objects to release
}
// Always release unmanaged objects
ClearStorage();
}
#region COM IDataObject Members
#region COM constants
private const int OLE_E_ADVISENOTSUPPORTED = unchecked((int)0x80040003);
private const int DV_E_FORMATETC = unchecked((int)0x80040064);
private const int DV_E_TYMED = unchecked((int)0x80040069);
private const int DV_E_CLIPFORMAT = unchecked((int)0x8004006A);
private const int DV_E_DVASPECT = unchecked((int)0x8004006B);
#endregion // COM constants
#region Unsupported functions
public int DAdvise(ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection)
{
throw Marshal.GetExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
}
public void DUnadvise(int connection)
{
throw Marshal.GetExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
}
public int EnumDAdvise(out IEnumSTATDATA enumAdvise)
{
throw Marshal.GetExceptionForHR(OLE_E_ADVISENOTSUPPORTED);
}
public int GetCanonicalFormatEtc(ref FORMATETC formatIn, out FORMATETC formatOut)
{
formatOut = formatIn;
return DV_E_FORMATETC;
}
public void GetDataHere(ref FORMATETC format, ref STGMEDIUM medium)
{
throw new NotSupportedException();
}
#endregion // Unsupported functions
/// <summary>
/// Gets an enumerator for the formats contained in this DataObject.
/// </summary>
/// <param name="direction">The direction of the data.</param>
/// <returns>An instance of the IEnumFORMATETC interface.</returns>
public IEnumFORMATETC EnumFormatEtc(DATADIR direction)
{
// We only support GET
if (DATADIR.DATADIR_GET == direction)
return new EnumFORMATETC(storage);
throw new NotImplementedException("OLE_S_USEREG");
}
/// <summary>
/// Gets the specified data.
/// </summary>
/// <param name="format">The requested data format.</param>
/// <param name="medium">When the function returns, contains the requested data.</param>
public void GetData(ref FORMATETC format, out STGMEDIUM medium)
{
// Locate the data
foreach (KeyValuePair<FORMATETC, STGMEDIUM> pair in storage)
{
if ((pair.Key.tymed & format.tymed) > 0
&& pair.Key.dwAspect == format.dwAspect
&& pair.Key.cfFormat == format.cfFormat)
{
// Found it. Return a copy of the data.
STGMEDIUM source = pair.Value;
medium = CopyMedium(ref source);
return;
}
}
// Didn't find it. Return an empty data medium.
medium = new STGMEDIUM();
}
/// <summary>
/// Determines if data of the requested format is present.
/// </summary>
/// <param name="format">The request data format.</param>
/// <returns>Returns the status of the request. If the data is present, S_OK is returned.
/// If the data is not present, an error code with the best guess as to the reason is returned.</returns>
public int QueryGetData(ref FORMATETC format)
{
// We only support CONTENT aspect
if ((DVASPECT.DVASPECT_CONTENT & format.dwAspect) == 0)
return DV_E_DVASPECT;
int ret = DV_E_TYMED;
// Try to locate the data
// TODO: The ret, if not S_OK, is only relevant to the last item
foreach (KeyValuePair<FORMATETC, STGMEDIUM> pair in storage)
{
if ((pair.Key.tymed & format.tymed) > 0)
{
if (pair.Key.cfFormat == format.cfFormat)
{
// Found it, return S_OK;
return 0;
}
else
{
// Found the medium type, but wrong format
ret = DV_E_CLIPFORMAT;
}
}
else
{
// Mismatch on medium type
ret = DV_E_TYMED;
}
}
return ret;
}
/// <summary>
/// Sets data in the specified format into storage.
/// </summary>
/// <param name="formatIn">The format of the data.</param>
/// <param name="medium">The data.</param>
/// <param name="release">If true, ownership of the medium's memory will be transferred
/// to this object. If false, a copy of the medium will be created and maintained, and
/// the caller is responsible for the memory of the medium it provided.</param>
public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)
{
// If the format exists in our storage, remove it prior to resetting it
foreach (KeyValuePair<FORMATETC, STGMEDIUM> pair in storage)
{
if ((pair.Key.tymed & formatIn.tymed) > 0
&& pair.Key.dwAspect == formatIn.dwAspect
&& pair.Key.cfFormat == formatIn.cfFormat)
{
storage.Remove(pair);
break;
}
}
// If release is true, we'll take ownership of the medium.
// If not, we'll make a copy of it.
STGMEDIUM sm = medium;
if (!release)
sm = CopyMedium(ref medium);
// Add it to the internal storage
KeyValuePair<FORMATETC, STGMEDIUM> addPair = new KeyValuePair<FORMATETC, STGMEDIUM>(formatIn, sm);
storage.Add(addPair);
}
/// <summary>
/// Creates a copy of the STGMEDIUM structure.
/// </summary>
/// <param name="medium">The data to copy.</param>
/// <returns>The copied data.</returns>
private STGMEDIUM CopyMedium(ref STGMEDIUM medium)
{
STGMEDIUM sm = new STGMEDIUM();
int hr = CopyStgMedium(ref medium, ref sm);
if (hr != 0)
throw Marshal.GetExceptionForHR(hr);
return sm;
}
#endregion
/// <summary>
/// Helps enumerate the formats available in our DataObject class.
/// </summary>
[ComVisible(true)]
private class EnumFORMATETC : IEnumFORMATETC
{
// Keep an array of the formats for enumeration
private FORMATETC[] formats;
// The index of the next item
private int currentIndex = 0;
/// <summary>
/// Creates an instance from a list of key value pairs.
/// </summary>
/// <param name="storage">List of FORMATETC/STGMEDIUM key value pairs</param>
internal EnumFORMATETC(IList<KeyValuePair<FORMATETC, STGMEDIUM>> storage)
{
// Get the formats from the list
formats = new FORMATETC[storage.Count];
for (int i = 0; i < formats.Length; i++)
formats[i] = storage[i].Key;
}
/// <summary>
/// Creates an instance from an array of FORMATETC's.
/// </summary>
/// <param name="formats">Array of formats to enumerate.</param>
private EnumFORMATETC(FORMATETC[] formats)
{
// Get the formats as a copy of the array
this.formats = new FORMATETC[formats.Length];
formats.CopyTo(this.formats, 0);
}
#region IEnumFORMATETC Members
/// <summary>
/// Creates a clone of this enumerator.
/// </summary>
/// <param name="newEnum">When this function returns, contains a new instance of IEnumFORMATETC.</param>
public void Clone(out IEnumFORMATETC newEnum)
{
EnumFORMATETC ret = new EnumFORMATETC(formats);
ret.currentIndex = currentIndex;
newEnum = ret;
}
/// <summary>
/// Retrieves the next elements from the enumeration.
/// </summary>
/// <param name="celt">The number of elements to retrieve.</param>
/// <param name="rgelt">An array to receive the formats requested.</param>
/// <param name="pceltFetched">An array to receive the number of element fetched.</param>
/// <returns>If the fetched number of formats is the same as the requested number, S_OK is returned.
/// There are several reasons S_FALSE may be returned: (1) The requested number of elements is less than
/// or equal to zero. (2) The rgelt parameter equals null. (3) There are no more elements to enumerate.
/// (4) The requested number of elements is greater than one and pceltFetched equals null or does not
/// have at least one element in it. (5) The number of fetched elements is less than the number of
/// requested elements.</returns>
public int Next(int celt, FORMATETC[] rgelt, int[] pceltFetched)
{
// Start with zero fetched, in case we return early
if (pceltFetched != null && pceltFetched.Length > 0)
pceltFetched[0] = 0;
// This will count down as we fetch elements
int cReturn = celt;
// Short circuit if they didn't request any elements, or didn't
// provide room in the return array, or there are not more elements
// to enumerate.
if (celt <= 0 || rgelt == null || currentIndex >= formats.Length)
return 1; // S_FALSE
// If the number of requested elements is not one, then we must
// be able to tell the caller how many elements were fetched.
if ((pceltFetched == null || pceltFetched.Length < 1) && celt != 1)
return 1; // S_FALSE
// If the number of elements in the return array is too small, we
// throw. This is not a likely scenario, hence the exception.
if (rgelt.Length < celt)
throw new ArgumentException("The number of elements in the return array is less than the number of elements requested");
// Fetch the elements.
for (int i = 0; currentIndex < formats.Length && cReturn > 0; i++, cReturn--, currentIndex++)
rgelt[i] = formats[currentIndex];
// Return the number of elements fetched
if (pceltFetched != null && pceltFetched.Length > 0)
pceltFetched[0] = celt - cReturn;
// cReturn has the number of elements requested but not fetched.
// It will be greater than zero, if multiple elements were requested
// but we hit the end of the enumeration.
return (cReturn == 0) ? 0 : 1; // S_OK : S_FALSE
}
/// <summary>
/// Resets the state of enumeration.
/// </summary>
/// <returns>S_OK</returns>
public int Reset()
{
currentIndex = 0;
return 0; // S_OK
}
/// <summary>
/// Skips the number of elements requested.
/// </summary>
/// <param name="celt">The number of elements to skip.</param>
/// <returns>If there are not enough remaining elements to skip, returns S_FALSE. Otherwise, S_OK is returned.</returns>
public int Skip(int celt)
{
if (currentIndex + celt > formats.Length)
return 1; // S_FALSE
currentIndex += celt;
return 0; // S_OK
}
#endregion
}
}
#endregion // DataObject
#region Native structures
[StructLayout(LayoutKind.Sequential)]
public struct Win32Point
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public struct Win32Size
{
public int cx;
public int cy;
}
[StructLayout(LayoutKind.Sequential)]
public struct ShDragImage
{
public Win32Size sizeDragImage;
public Win32Point ptOffset;
public IntPtr hbmpDragImage;
public int crColorKey;
}
#endregion // Native structures
#region IDragSourceHelper
[ComVisible(true)]
[ComImport]
[Guid("DE5BF786-477A-11D2-839D-00C04FD918D0")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDragSourceHelper
{
void InitializeFromBitmap(
[In, MarshalAs(UnmanagedType.Struct)] ref ShDragImage dragImage,
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject);
void InitializeFromWindow(
[In] IntPtr hwnd,
[In] ref Win32Point pt,
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject);
}
#endregion // IDragSourceHelper
#region IDropTargetHelper
[ComVisible(true)]
[ComImport]
[Guid("4657278B-411B-11D2-839A-00C04FD918D0")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IDropTargetHelper
{
void DragEnter(
[In] IntPtr hwndTarget,
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject,
[In] ref Win32Point pt,
[In] int effect);
void DragLeave();
void DragOver(
[In] ref Win32Point pt,
[In] int effect);
void Drop(
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject,
[In] ref Win32Point pt,
[In] int effect);
void Show(
[In] bool show);
}
#endregion // IDropTargetHelper
#region DragDropHelper
[ComImport]
[Guid("4657278A-411B-11d2-839A-00C04FD918D0")]
public class DragDropHelper { }
#endregion // DragDropHelper
}
#region SWF extensions
#region IDataObject extensions
namespace System.Windows.Forms
{
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using DragDropLib;
using ComIDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using Point = System.Drawing.Point;
public static class SwfDataObjectExtensions
{
#region DLL imports
[DllImport("gdiplus.dll")]
private static extern bool DeleteObject(IntPtr hgdi);
#endregion // DLL imports
/// <summary>
/// Sets the drag image as the rendering of a control.
/// </summary>
/// <param name="dataObject">The DataObject to set the drag image on.</param>
/// <param name="control">The Control to render as the drag image.</param>
/// <param name="cursorOffset">The ___location of the cursor relative to the control.</param>
public static void SetDragImage(this IDataObject dataObject, Control control, System.Drawing.Point cursorOffset)
{
int width = control.Width;
int height = control.Height;
Bitmap bmp = new Bitmap(width, height);
control.DrawToBitmap(bmp, new Rectangle(0, 0, width, height));
SetDragImage(dataObject, bmp, cursorOffset);
}
/// <summary>
/// Sets the drag image.
/// </summary>
/// <param name="dataObject">The DataObject to set the drag image on.</param>
/// <param name="image">The drag image.</param>
/// <param name="cursorOffset">The ___location of the cursor relative to the image.</param>
public static void SetDragImage(this IDataObject dataObject, Image image, System.Drawing.Point cursorOffset)
{
ShDragImage shdi = new ShDragImage();
Win32Size size;
size.cx = image.Width;
size.cy = image.Height;
shdi.sizeDragImage = size;
Win32Point wpt;
wpt.x = cursorOffset.X;
wpt.y = cursorOffset.Y;
shdi.ptOffset = wpt;
shdi.crColorKey = Color.Magenta.ToArgb();
// This HBITMAP will be managed by the DragDropHelper
// as soon as we pass it to InitializeFromBitmap. If we fail
// to make the hand off, we'll delete it to prevent a mem leak.
IntPtr hbmp = GetHbitmapFromImage(image);
shdi.hbmpDragImage = hbmp;
try
{
IDragSourceHelper sourceHelper = (IDragSourceHelper)new DragDropHelper();
try
{
sourceHelper.InitializeFromBitmap(ref shdi, (ComIDataObject)dataObject);
}
catch (NotImplementedException ex)
{
throw new Exception("A NotImplementedException was caught. This could be because you forgot to construct your DataObject using a DragDropLib.DataObject", ex);
}
}
catch
{
DeleteObject(hbmp);
}
}
/// <summary>
/// Gets an HBITMAP from any image.
/// </summary>
/// <param name="image">The image to get an HBITMAP from.</param>
/// <returns>An HBITMAP pointer.</returns>
/// <remarks>
/// The caller is responsible to call DeleteObject on the HBITMAP.
/// </remarks>
private static IntPtr GetHbitmapFromImage(Image image)
{
if (image is Bitmap)
{
return ((Bitmap)image).GetHbitmap();
}
else
{
Bitmap bmp = new Bitmap(image);
return bmp.GetHbitmap();
}
}
}
}
#endregion // IDataObject extensions
#region DragDropLib extensions
namespace DragDropLib
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
public static class SwfDragDropLibExtensions
{
/// <summary>
/// Converts a System.Windows.Point value to a DragDropLib.Win32Point value.
/// </summary>
/// <param name="pt">Input value.</param>
/// <returns>Converted value.</returns>
public static Win32Point ToWin32Point(this Point pt)
{
Win32Point wpt = new Win32Point();
wpt.x = pt.X;
wpt.y = pt.Y;
return wpt;
}
}
}
#endregion // DragDropLib extensions
#region IDropTargetHelper extensions
namespace DragDropLib
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using ComIDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
public static class SwfDropTargetHelperExtensions
{
/// <summary>
/// Notifies the DragDropHelper that the specified Control received
/// a DragEnter event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="control">The Control the received the DragEnter event.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragEnter(this IDropTargetHelper dropHelper, Control control, IDataObject data, Point cursorOffset, DragDropEffects effect)
{
IntPtr controlHandle = IntPtr.Zero;
if (control != null)
controlHandle = control.Handle;
Win32Point pt = SwfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.DragEnter(controlHandle, (ComIDataObject)data, ref pt, (int)effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Control received
/// a DragOver event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragOver(this IDropTargetHelper dropHelper, Point cursorOffset, DragDropEffects effect)
{
Win32Point pt = SwfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.DragOver(ref pt, (int)effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Control received
/// a Drop event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void Drop(this IDropTargetHelper dropHelper, IDataObject data, Point cursorOffset, DragDropEffects effect)
{
Win32Point pt = SwfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.Drop((ComIDataObject)data, ref pt, (int)effect);
}
}
}
#endregion // IDropTargetHelper extensions
#region DropTargetHelper class
namespace System.Windows.Forms
{
using System;
using System.Drawing;
using System.Windows.Forms;
using DragDropLib;
public static class DropTargetHelper
{
/// <summary>
/// Internal instance of the DragDropHelper.
/// </summary>
private static IDropTargetHelper s_instance = (IDropTargetHelper)new DragDropHelper();
static DropTargetHelper()
{
}
/// <summary>
/// Notifies the DragDropHelper that the specified Control received
/// a DragEnter event.
/// </summary>
/// <param name="control">The Control the received the DragEnter event.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragEnter(Control control, IDataObject data, System.Drawing.Point cursorOffset, DragDropEffects effect)
{
SwfDropTargetHelperExtensions.DragEnter(s_instance, control, data, cursorOffset, effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Control received
/// a DragOver event.
/// </summary>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragOver(System.Drawing.Point cursorOffset, DragDropEffects effect)
{
SwfDropTargetHelperExtensions.DragOver(s_instance, cursorOffset, effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Control received
/// a DragLeave event.
/// </summary>
public static void DragLeave()
{
s_instance.DragLeave();
}
/// <summary>
/// Notifies the DragDropHelper that the current Control received
/// a DragOver event.
/// </summary>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void Drop(IDataObject data, System.Drawing.Point cursorOffset, DragDropEffects effect)
{
SwfDropTargetHelperExtensions.Drop(s_instance, data, cursorOffset, effect);
}
/// <summary>
/// Tells the DragDropHelper to show or hide the drag image.
/// </summary>
/// <param name="show">True to show the image. False to hide it.</param>
public static void Show(bool show)
{
s_instance.Show(show);
}
}
}
#endregion // DropTargetHelper class
#endregion // SWF extensions
#region WPF extensions
#region IDataObject extensions
namespace System.Windows
{
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using DragDropLib;
using Color = System.Windows.Media.Color;
using ComIDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using DrawingColor = System.Drawing.Color;
using DrawingColorPalette = System.Drawing.Imaging.ColorPalette;
using DrawingPixelFormat = System.Drawing.Imaging.PixelFormat;
using PixelFormat = System.Windows.Media.PixelFormat;
using DrawingRectangle = System.Drawing.Rectangle;
using System.Drawing.Imaging;
using System.Windows.Controls;
using Bitmap = System.Drawing.Bitmap;
public static class WpfDataObjectExtensions
{
#region DLL imports
[DllImport("gdiplus.dll")]
private static extern bool DeleteObject(IntPtr hgdi);
#endregion // DLL imports
/// <summary>
/// Sets the drag image by rendering the specified UIElement.
/// </summary>
/// <param name="dataObject">The DataObject to set the drag image for.</param>
/// <param name="element">The element to render as the drag image.</param>
/// <param name="cursorOffset">The offset of the cursor relative to the UIElement.</param>
public static void SetDragImage(this IDataObject dataObject, UIElement element, Point cursorOffset)
{
Size size = element.RenderSize;
// Get the device's DPI so we render at full size
int dpix, dpiy;
GetDeviceDpi(element, out dpix, out dpiy);
// Create our renderer at full size
RenderTargetBitmap renderSource = new RenderTargetBitmap(
(int)size.Width, (int)size.Height, dpix, dpiy, PixelFormats.Pbgra32);
// Render the element
renderSource.Render(element);
// Set the drag image by the bitmap source
SetDragImage(dataObject, renderSource, cursorOffset);
}
/// <summary>
/// Sets the drag image from a BitmapSource.
/// </summary>
/// <param name="dataObject">The DataObject on which to set the drag image.</param>
/// <param name="image">The image source.</param>
/// <param name="cursorOffset">The offset relative to the bitmap image.</param>
public static void SetDragImage(this IDataObject dataObject, BitmapSource image, Point cursorOffset)
{
// Our internal routine requires an HBITMAP, so we'll convert the
// BitmapSource to a System.Drawing.Bitmap.
Bitmap bmp = GetBitmapFromBitmapSource(image, Colors.Magenta);
// Sets the drag image from a Bitmap
SetDragImage(dataObject, bmp, cursorOffset);
}
/// <summary>
/// Sets the drag image.
/// </summary>
/// <param name="dataObject">The DataObject to set the drag image on.</param>
/// <param name="image">The drag image.</param>
/// <param name="cursorOffset">The ___location of the cursor relative to the image.</param>
private static void SetDragImage(this IDataObject dataObject, Bitmap bitmap, Point cursorOffset)
{
ShDragImage shdi = new ShDragImage();
Win32Size size;
size.cx = bitmap.Width;
size.cy = bitmap.Height;
shdi.sizeDragImage = size;
Win32Point wpt;
wpt.x = (int)cursorOffset.X;
wpt.y = (int)cursorOffset.Y;
shdi.ptOffset = wpt;
shdi.crColorKey = DrawingColor.Magenta.ToArgb();
// This HBITMAP will be managed by the DragDropHelper
// as soon as we pass it to InitializeFromBitmap. If we fail
// to make the hand off, we'll delete it to prevent a mem leak.
IntPtr hbmp = bitmap.GetHbitmap();
shdi.hbmpDragImage = hbmp;
try
{
IDragSourceHelper sourceHelper = (IDragSourceHelper)new DragDropHelper();
try
{
sourceHelper.InitializeFromBitmap(ref shdi, (ComIDataObject)dataObject);
}
catch (NotImplementedException ex)
{
throw new Exception("A NotImplementedException was caught. This could be because you forgot to construct your DataObject using a DragDropLib.DataObject", ex);
}
}
catch
{
// We failed to initialize the drag image, so the DragDropHelper
// won't be managing our memory. Release the HBITMAP we allocated.
DeleteObject(hbmp);
}
}
#region Helper methods
/// <summary>
/// Gets the device capabilities.
/// </summary>
/// <param name="reference">A reference UIElement for getting the relevant device caps.</param>
/// <param name="dpix">The horizontal DPI.</param>
/// <param name="dpiy">The vertical DPI.</param>
private static void GetDeviceDpi(Visual reference, out int dpix, out int dpiy)
{
Matrix m = PresentationSource.FromVisual(reference).CompositionTarget.TransformToDevice;
dpix = (int)(96 * m.M11);
dpiy = (int)(96 * m.M22);
}
/// <summary>
/// Gets a System.Drawing.Bitmap from a BitmapSource.
/// </summary>
/// <param name="source">The source image from which to create our Bitmap.</param>
/// <param name="transparencyKey">The transparency key. This is used by the DragDropHelper
/// in rendering transparent pixels.</param>
/// <returns>An instance of Bitmap which is a copy of the BitmapSource's image.</returns>
private static Bitmap GetBitmapFromBitmapSource(BitmapSource source, Color transparencyKey)
{
// Copy at full size
Int32Rect sourceRect = new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight);
// Convert to our destination pixel format
DrawingPixelFormat pxFormat = ConvertPixelFormat(source.Format);
// Create the Bitmap, full size, full rez
Bitmap bmp = new Bitmap(sourceRect.Width, sourceRect.Height, pxFormat);
// If the format is an indexed format, copy the color palette
if ((pxFormat & DrawingPixelFormat.Indexed) == DrawingPixelFormat.Indexed)
ConvertColorPalette(bmp.Palette, source.Palette);
// Get the transparency key as a System.Drawing.Color
DrawingColor transKey = transparencyKey.ToDrawingColor();
// Lock our Bitmap bits, we need to write to it
BitmapData bmpData = bmp.LockBits(
sourceRect.ToDrawingRectangle(),
ImageLockMode.ReadWrite,
pxFormat);
{
// Copy the source bitmap data to our new Bitmap
source.CopyPixels(sourceRect, bmpData.Scan0, bmpData.Stride * sourceRect.Height, bmpData.Stride);
// The drag image seems to work in full 32-bit color, except when
// alpha equals zero. Then it renders those pixels at black. So
// we make a pass and set all those pixels to the transparency key
// color. This is only implemented for 32-bit pixel colors for now.
if ((pxFormat & DrawingPixelFormat.Alpha) == DrawingPixelFormat.Alpha)
ReplaceTransparentPixelsWithTransparentKey(bmpData, transKey);
}
// Done, unlock the bits
bmp.UnlockBits(bmpData);
return bmp;
}
/// <summary>
/// Replaces any pixel with a zero alpha value with the specified transparency key.
/// </summary>
/// <param name="bmpData">The bitmap data in which to perform the operation.</param>
/// <param name="transKey">The transparency color. This color is rendered transparent
/// by the DragDropHelper.</param>
/// <remarks>
/// This function only supports 32-bit pixel formats for now.
/// </remarks>
private static void ReplaceTransparentPixelsWithTransparentKey(BitmapData bmpData, DrawingColor transKey)
{
DrawingPixelFormat pxFormat = bmpData.PixelFormat;
if (DrawingPixelFormat.Format32bppArgb == pxFormat
|| DrawingPixelFormat.Format32bppPArgb == pxFormat)
{
int transKeyArgb = transKey.ToArgb();
// We will just iterate over the data... we don't care about pixel ___location,
// just that every pixel is checked.
unsafe
{
byte* pscan = (byte*)bmpData.Scan0.ToPointer();
{
for (int y = 0; y < bmpData.Height; ++y, pscan += bmpData.Stride)
{
int* prgb = (int*)pscan;
for (int x = 0; x < bmpData.Width; ++x, ++prgb)
{
// If the alpha value is zero, replace this pixel's color
// with the transparency key.
if ((*prgb & 0xFF000000L) == 0L)
*prgb = transKeyArgb;
}
}
}
}
}
else
{
// If it is anything else, we aren't supporting it, but we
// won't throw, cause it isn't an error
System.Diagnostics.Trace.TraceWarning("Not converting transparent colors to transparency key.");
return;
}
}
/// <summary>
/// Converts a System.Windows.Media.Color to System.Drawing.Color.
/// </summary>
/// <param name="color">System.Windows.Media.Color value to convert.</param>
/// <returns>System.Drawing.Color value.</returns>
private static DrawingColor ToDrawingColor(this Color color)
{
return DrawingColor.FromArgb(
color.A, color.R, color.G, color.B);
}
/// <summary>
/// Converts a System.Windows.Int32Rect to a System.Drawing.Rectangle value.
/// </summary>
/// <param name="rect">The System.Windows.Int32Rect to convert.</param>
/// <returns>The System.Drawing.Rectangle converted value.</returns>
private static DrawingRectangle ToDrawingRectangle(this Int32Rect rect)
{
return new DrawingRectangle(rect.X, rect.Y, rect.Width, rect.Height);
}
/// <summary>
/// Converts the entries in a BitmapPalette to ColorPalette entries.
/// </summary>
/// <param name="destPalette">ColorPalette destination palette.</param>
/// <param name="bitmapPalette">BitmapPalette source palette.</param>
private static void ConvertColorPalette(DrawingColorPalette destPalette, BitmapPalette bitmapPalette)
{
DrawingColor[] destEntries = destPalette.Entries;
IList<Color> sourceEntries = bitmapPalette.Colors;
if (destEntries.Length < sourceEntries.Count)
throw new ArgumentException("Destination palette has less entries than the source palette");
for (int i = 0, count = sourceEntries.Count; i < count; ++i)
destEntries[i] = sourceEntries[i].ToDrawingColor();
}
/// <summary>
/// Converts a System.Windows.Media.PixelFormat instance to a
/// System.Drawing.Imaging.PixelFormat value.
/// </summary>
/// <param name="pixelFormat">The input PixelFormat.</param>
/// <returns>The converted value.</returns>
private static DrawingPixelFormat ConvertPixelFormat(PixelFormat pixelFormat)
{
if (PixelFormats.Bgr24 == pixelFormat)
return DrawingPixelFormat.Format24bppRgb;
if (PixelFormats.Bgr32 == pixelFormat)
return DrawingPixelFormat.Format32bppRgb;
if (PixelFormats.Bgr555 == pixelFormat)
return DrawingPixelFormat.Format16bppRgb555;
if (PixelFormats.Bgr565 == pixelFormat)
return DrawingPixelFormat.Format16bppRgb565;
if (PixelFormats.Bgra32 == pixelFormat)
return DrawingPixelFormat.Format32bppArgb;
if (PixelFormats.BlackWhite == pixelFormat)
return DrawingPixelFormat.Format1bppIndexed;
if (PixelFormats.Gray16 == pixelFormat)
return DrawingPixelFormat.Format16bppGrayScale;
if (PixelFormats.Indexed1 == pixelFormat)
return DrawingPixelFormat.Format1bppIndexed;
if (PixelFormats.Indexed4 == pixelFormat)
return DrawingPixelFormat.Format4bppIndexed;
if (PixelFormats.Indexed8 == pixelFormat)
return DrawingPixelFormat.Format8bppIndexed;
if (PixelFormats.Pbgra32 == pixelFormat)
return DrawingPixelFormat.Format32bppPArgb;
if (PixelFormats.Prgba64 == pixelFormat)
return DrawingPixelFormat.Format64bppPArgb;
if (PixelFormats.Rgb24 == pixelFormat)
return DrawingPixelFormat.Format24bppRgb;
if (PixelFormats.Rgb48 == pixelFormat)
return DrawingPixelFormat.Format48bppRgb;
if (PixelFormats.Rgba64 == pixelFormat)
return DrawingPixelFormat.Format64bppArgb;
throw new NotSupportedException("The pixel format of the source bitmap is not supported.");
}
#endregion // Helper methods
}
}
#endregion // IDataObject extensions
#region DragDropLib extensions
namespace DragDropLib
{
using System;
using System.Windows;
public static class WpfDragDropLibExtensions
{
/// <summary>
/// Converts a System.Windows.Point value to a DragDropLib.Win32Point value.
/// </summary>
/// <param name="pt">Input value.</param>
/// <returns>Converted value.</returns>
public static Win32Point ToWin32Point(this Point pt)
{
Win32Point wpt = new Win32Point();
wpt.x = (int)pt.X;
wpt.y = (int)pt.Y;
return wpt;
}
}
}
#endregion // DragDropLib extensions
#region IDropTargetHelper extensions
namespace DragDropLib
{
using System;
using System.Windows;
using ComIDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using System.Windows.Interop;
public static class WpfDropTargetHelperExtensions
{
/// <summary>
/// Notifies the DragDropHelper that the specified Window received
/// a DragEnter event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="window">The Window the received the DragEnter event.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragEnter(this IDropTargetHelper dropHelper, Window window, IDataObject data, Point cursorOffset, DragDropEffects effect)
{
IntPtr windowHandle = IntPtr.Zero;
if (window != null)
windowHandle = (new WindowInteropHelper(window)).Handle;
Win32Point pt = WpfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.DragEnter(windowHandle, (ComIDataObject)data, ref pt, (int)effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Window received
/// a DragOver event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragOver(this IDropTargetHelper dropHelper, Point cursorOffset, DragDropEffects effect)
{
Win32Point pt = WpfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.DragOver(ref pt, (int)effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Window received
/// a Drop event.
/// </summary>
/// <param name="dropHelper">The DragDropHelper instance to notify.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void Drop(this IDropTargetHelper dropHelper, IDataObject data, Point cursorOffset, DragDropEffects effect)
{
Win32Point pt = WpfDragDropLibExtensions.ToWin32Point(cursorOffset);
dropHelper.Drop((ComIDataObject)data, ref pt, (int)effect);
}
}
}
#endregion // IDropTargetHelper extensions
#region DropTargetHelper class
namespace System.Windows
{
using System;
using System.Windows;
using DragDropLib;
public static class DropTargetHelper
{
/// <summary>
/// Internal instance of the DragDropHelper.
/// </summary>
private static IDropTargetHelper s_instance = (IDropTargetHelper)new DragDropHelper();
static DropTargetHelper()
{
}
/// <summary>
/// Notifies the DragDropHelper that the specified Window received
/// a DragEnter event.
/// </summary>
/// <param name="window">The Window the received the DragEnter event.</param>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragEnter(Window window, IDataObject data, Point cursorOffset, DragDropEffects effect)
{
WpfDropTargetHelperExtensions.DragEnter(s_instance, window, data, cursorOffset, effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Window received
/// a DragOver event.
/// </summary>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void DragOver(Point cursorOffset, DragDropEffects effect)
{
WpfDropTargetHelperExtensions.DragOver(s_instance, cursorOffset, effect);
}
/// <summary>
/// Notifies the DragDropHelper that the current Window received
/// a DragLeave event.
/// </summary>
public static void DragLeave()
{
s_instance.DragLeave();
}
/// <summary>
/// Notifies the DragDropHelper that the current Window received
/// a DragOver event.
/// </summary>
/// <param name="data">The DataObject containing a drag image.</param>
/// <param name="cursorOffset">The current cursor's offset relative to the window.</param>
/// <param name="effect">The accepted drag drop effect.</param>
public static void Drop(IDataObject data, Point cursorOffset, DragDropEffects effect)
{
WpfDropTargetHelperExtensions.Drop(s_instance, data, cursorOffset, effect);
}
/// <summary>
/// Tells the DragDropHelper to show or hide the drag image.
/// </summary>
/// <param name="show">True to show the image. False to hide it.</param>
public static void Show(bool show)
{
s_instance.Show(show);
}
}
}
#endregion // DropTargetHelper class
#endregion // WPF extensions