using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace DragDropLib
{
#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
}