Shell Style Drag and Drop in .NET – Part 3

Series Links

This is part of a 3 part series:

  1. Shell Style Drag and Drop in .NET (WPF and WinForms)
  2. Shell Style Drag and Drop in .NET - Part 2
  3. Shell Style Drag and Drop in .NET - Part 3


In Part 1, Shell Style Drag and Drop in .NET (WPF and WinForms), I opened up the discussion about implementing a nice Shell style drag image, like that of Windows Explorer, in C#. This involved exposing a couple of COM interfaces to .NET, as well as implementing the COM IDataObject interface. In Shell Style Drag and Drop in .NET - Part 2 I took it to the next step, implementing some extension methods and a helper class. These made it easy to use the COM interfaces to achieve Shell integration.

In Part 3, I will open the door even wider. My aim is to achieve complete Shell integration. First, I will look at adding a drop description. This is the little preview text that tells you what you would do by dropping at the current location. It is a dynamic text with an icon, indicating whether you are moving, copying, linking, etc. Then I'll look at handling all the drag source plumbing in a consistent fashion through the use of a new class, the DragSourceHelper. Unlike the DropTargetHelper class from Part 2, the DragSourceHelper actually has a decent amount of work to do.


In Parts 1 and 2 I utilized the IDragSourceHelper COM interface exposed by the Windows Shell. In this part we will take a look at IDragSourceHelper2, which inherits IDragSourceHelper and exposed an additional function, SetFlags, for allowing drop descriptions.

  • IDragSourceHelper2 - Exposes a method that adds functionality to IDragSourceHelper. This method sets the characteristics of a drag-and-drop operation over an IDragSourceHelper object.

The interface, like IDragSourceHelper, is implemented by the system DragDropHelper class, which is CoCreated using CLSID_DragDropHelper.

The Solution

The solution for this post is quite large, because we are bringing everything together for a complete solution. The solution comes in several parts, in each of which we'll address a major requirement for the overall solution.

Finishing the Implementation with Drop Descriptions

To get drop descriptions working, I need to set a flag on the drag image manager. To do this, I use the IDragSourceHelper2 interface:

public interface IDragSourceHelper2
void InitializeFromBitmap(
[In, MarshalAs(UnmanagedType.Struct)]
ref ShDragImage dragImage,
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject);

void InitializeFromWindow(
[In] IntPtr hwnd,
ref Win32Point pt,
[In, MarshalAs(UnmanagedType.Interface)] IDataObject dataObject);

void SetFlags(
int dwFlags);

The IDragSourceHelper2 interface provides the SetFlags function. According to MSDN, there is only one flag accepted by this function, and that is DSH_ALLOWDROPDESCRIPTIONTEXT which is defined in shlobjidl.h as 0x1. We will utilize this method solely to enable drop descriptions.

I also need to introduce a native structure, DROPDESCRIPTION, as a managed structure:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Size = 1044)]
public struct DropDescription
public int type;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst
= 260)]
public string szMessage;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst
= 260)]
public string szInsert;

Note that the size of the szMessage and szInsert fields is limited to 260 Unicode characters during marshaling. When I set the drop description, this is the structure that I need to set onto the IDataObject for the drag image manager to read and render from.

I also need a new enum, which I've introduced separately in the System.Windows and System.Windows.Forms namespaces for ease of use:

public enum DropImageType
= -1,
= 0,
= (int)DragDropEffects.Copy,
= (int)DragDropEffects.Move,
= (int)DragDropEffects.Link,
= 6,
= 7

Not much to say here. These are the values for the "type" field on the DropDescription structure. Many of the values correlate directly to the DragDropEffects enum.

To set the drop description on the data object, I introduce an extension method to the SWF and WPF IDataObject interfaces. As in previous parts, I will use the SWF implementation in my examples:

public static void SetDropDescription(this IDataObject dataObject, DropImageType type, string format, string insert)
if (format != null && format.Length > 259)
throw new ArgumentException("Format string exceeds the maximum allowed length of 259.", "format");
if (insert != null && insert.Length > 259)
throw new ArgumentException("Insert string exceeds the maximum allowed length of 259.", "insert");

// Fill the structure
DropDescription dd;
= (int)type;
= format;
= insert;

ComTypes.ComDataObjectExtensions.SetDropDescription((ComTypes.IDataObject)dataObject, dd);

Here I accept a DropImageType enum value, a format string and an insert string. See the documentation for DROPDESCRIPTION on MSDN for details about format and insert, but basically know that you can specify a format like "Move to %1" where "%1" gets replaced with the insert string upon rendering. This is to distinguish the verb from the location, for example. The drag image manager renders the parts of the drop description slightly different to achieve the distinction.

This method leaves some of the work to the ComDataObjectExtensions.SetDropDescription method, which is actually an extension method for the COM IDataObject interface. I separated the logic like this because my SWF and WPF implementations can both take advantage of it. Let's look at that method now:

public static void SetDropDescription(this IDataObject dataObject, DropDescription dropDescription)
ComTypes.FORMATETC formatETC;
FillFormatETC(DropDescriptionFormat, TYMED.TYMED_HGLOBAL,
out formatETC);

// We need to set the drop description as an HGLOBAL.
// Allocate space ...
IntPtr pDD = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(DropDescription)));
// ... and marshal the data
Marshal.StructureToPtr(dropDescription, pDD, false);

// The medium wraps the HGLOBAL
System.Runtime.InteropServices.ComTypes.STGMEDIUM medium;
= null;
= pDD;

// Set the data
ComTypes.IDataObject dataObjectCOM = (ComTypes.IDataObject)dataObject;
ref formatETC, ref medium, true);
// If we failed, we need to free the HGLOBAL memory

In this method I marshal the structure to an HGLOBAL and then set the data on the COM IDataObject instance.

Ok, there is one last thing to do, which is a bit more obscure. For the drag image manager to properly re-render the drag image with changing drop descriptions, I need to invalidate the drag image by sending a Window message to the drag window. The drag manager provides the drag window's HWND in the data format "DragWindow" on my IDataObject, so I can grab that and call the SendMessage Win32 API:

private const uint WM_INVALIDATEDRAGIMAGE = 0x403;

public static void InvalidateDragImage(IDataObject dataObject)
if (dataObject.GetDataPresent("DragWindow"))
IntPtr hwnd
= GetIntPtrFromData(dataObject.GetData("DragWindow"));
PostMessage(hwnd, WM_INVALIDATEDRAGIMAGE, IntPtr.Zero, IntPtr.Zero);

private static IntPtr GetIntPtrFromData(object data)
byte[] buf = null;

if (data is MemoryStream)
= new byte[4];
if (4 != ((MemoryStream)data).Read(buf, 0, 4))
throw new ArgumentException("Could not read an IntPtr from the MemoryStream");
if (data is byte[])
= (byte[])data;
if (buf.Length < 4)
throw new ArgumentException("Could not read an IntPtr from the byte array");

if (buf == null)
throw new ArgumentException("Could not read an IntPtr from the " + data.GetType().ToString());

int p = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0];
return new IntPtr(p);

You won't find WM_INVALIDATEDRAGIMAGE in documentation, its value is actually WM_USER + 3. I don't remember where I found this, but it works. Would be interesting to know if there are other messages that the drag window accepts to get around some of the problems I'll touch on later.

This InvalidateDragImage method should be called by your drag source's GiveFeedback event handler. I'll walk you through a more complete GiveFeedback event handler later when I talk about the DragSourceHelper class.

Drop Descriptions Summary

Well, that's that. With these new foundations, I can enable and set drop descriptions for my drag images in my application. To enable the drop descriptions, I need to make sure to call SetFlags with an argument of 1 before I set the drag image via IDragSourceHelper.InitializeFromBitmap or IDragSourceHelper.InitializeFromWindow. To set the drop description, during DragEnter or DragOver events I can call e.Data.SetDropDescription with my desired arguments. The last thing to remember is that the drag source needs to listen to the GiveFeedback event and invalidate the drag window.

What Happened to the Managed SetData?

In Parts 1 and 2 when I implemented the COM IDataObject and set it as the inner data store for the SWF and WPF DataObjects, I forgot to address one issue. Both the SWF and WPF versions of DataObject implement both the respective IDataObject and the COM IDataObject. The reason I had to implement the COM IDataObject myself is that the .NET DataObjects don't allow setting data by calling the COM IDataObject.SetData method. They throw a COMException with E_NOTIMPL, which is basically a NotImplementedException. That prevented me from being able to use the .NET DataObjects because the Shell DragDropHelper requires the ability to call that COM callable method to store information for the drag image manager. However, the .NET DataObjects do allow you to provide a COM IDataObject to the constructors, and then would allow me to pass COM calls to the wrapped object directly. That is exactly how I implemented my solution.

(Note: I use ".NET SetData" and "COM SetData" to distinguish the SetData methods exposed by the .NET IDataObject and COM IDataObject respectively. The .NET SetData method is the one you, as a .NET developer, usually call. The COM SetData method is the one that is exposed by the COM IDataObject and is COM-callable. Also, where I've used SWF and/or WPF in the past, I will summarize as just ".NET". The two implementations of data objects are very very similar. So, when I say ".NET IDataObject interface(s)" I mean SWF or WPF, they are pretty much interchangable.)

Well, that put a new block on me. Now, the .NET DataObject that wrapped my COM IDataObject doesn't allow a .NET SetData call. It throws an InvalidOperationException. It is actually an internal converter class that throws the exception. The internal converter class implements the .NET IDataObject interface, and receives the .NET SetData call, but does nothing except throw an exception. The converter class gets created when I pass the COM IDataObject to the constructor of the .NET DataObject, which holds onto it as an internal data store, instead of the default internal data store. I drew up a diagram to show where my .NET SetData calls get blocked:

COM IDataObject Wrapped by .NET DataObject

Notice that there is a converter data object wrapping the COM IDataObject, and that is where the .NET SetData call fails. All other GetData and SetData calls go through as expected.

So, why not just implement the .NET IDataObject as well? Well, I certainly went down that path. Unfortunately, when you call DoDragDrop, which accepts the .NET IDataObject interface, as opposed to the DataObject class, somewhere along the line it gets wrapped by a .NET DataObject anyway. This wouldn't be a big deal, except there is another problem. The .NET DataObject classes seem to be less robust than I think they were intended. When I pass an instance of a class that implements both the .NET IDataObject interface as well as the COM IDataObject interface to the constructor, the .NET DataObject recognizes it as a .NET IDataObject and references it directly as its internal data store. That's great! Right? Well, no. The COM SetData method of the .NET DataObjects, as you can see in the above diagram, get a direct reference to the COM IDataObject of the internal converter data object class. Well, if the internal data store isn't of that type, then it will throw an exception, the same one I saw that made me implement the COM IDataObject interface to begin with. Here is a diagram showing this situation:

.NET/COM IDataObject Wrapped by .NET DataObject

As you can see, by the .NET DataObject classes not noticing that the internal data store is a COM IDataObject as well as a .NET IDataObject, I can't utilize both versions of SetData. So now what? Well, we only really have one choice. Because we require the COM SetData to function, we need to accept the fact that the .NET SetData won't. At least, not directly. Luckily, I have complete control, as the creator of the DataObject, and so I'll just write a new extension function, SetDataEx, which will accept managed data, marshal it to an HGLOBAL, and set it using the COM SetData. Let's get to it!

I don't need a completely robust solution, because in many cases, I can use the .NET DataObject classes as a converter. But I'll come back to that in a bit. For now, I need a function that takes managed data, marshals it, and sets it to the COM IDataObject using the COM SetData method. This seems like a great candidate for an extension method on the COM IDataObject interface, so that's exactly how I'll implement it. Note, this is not the same as the SetDataEx function I mentioned, which will use this method internally:

public static void SetManagedData(this IDataObject dataObject, string format, object data)
// Initialize the format structure
ComTypes.FORMATETC formatETC;
out formatETC);

// Serialize/marshal our data into an unmanaged medium
ComTypes.STGMEDIUM medium;
out medium);
// Set the data on our data object
dataObject.SetData(ref formatETC, ref medium, true);
// On exceptions, release the medium
ReleaseStgMedium(ref medium);

This method really handles just the conversion of a string format to the FORMATETC structure. The meat of the marshaling is in the GetMediumFromObject function:

private static void GetMediumFromObject(object data, out STGMEDIUM medium)
// We'll serialize to a managed stream temporarily
MemoryStream stream = new MemoryStream();

// Write an indentifying stamp, so we can recognize this as custom
// marshaled data.
stream.Write(ManagedDataStamp.ToByteArray(), 0, Marshal.SizeOf(typeof(Guid)));

// Now serialize the data. Note, if the data is not directly serializable,
// we'll try type conversion. Also, we serialize the type. That way,
// during deserialization, we know which type to convert back to, if
// appropriate.
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, data.GetType());
formatter.Serialize(stream, GetAsSerializable(data));

// Now copy to an HGLOBAL
byte[] bytes = stream.GetBuffer();
IntPtr p
= Marshal.AllocHGlobal(bytes.Length);
0, p, bytes.Length);
// Make sure to free the memory on exceptions

// Now allocate an STGMEDIUM to wrap the HGLOBAL
medium.unionmember = p;
= null;

I just create a MemoryStream, which I will serialize my object to, and then write an internally defined GUID to it. This GUID is a stamp for my code to recognize the data by when I go to get the data. I didn't mention it before, but the .NET GetData will only partially work for me. If a type is serializable (implements ISerializable or is marked by the SerializableAttribute) then the .NET GetData method will still work. If not, I'll have to use another new function, GetDataEx to get my data. We'll see that later. After the stamp, I serialize the type, which I'll need during deserialization because the actual data I serialize may be converted. Then, I use a helper method, GetAsSerializable, that will determine if the data's type is serializable, and if not, will try to convert it to a string using a type converter that the type may have declared through the TypeConverterAttribute. I won't show that code here, because it isn't directly relevant to drag and drop. Finally, I allocate an HGLOBAL and fill the STGMEDIUM structure before returning.

The SetDataEx function is an extension method on the .NET IDataObject interfaces:

public static void SetDataEx(this IDataObject dataObject, string format, object data)
DataFormats.Format dataFormat
= DataFormats.GetFormat(format);

// Initialize the format structure
ComTypes.FORMATETC formatETC = new ComTypes.FORMATETC();
= (short)dataFormat.Id;
= -1;
= IntPtr.Zero;

// Try to discover the TYMED from the format and data
ComTypes.TYMED tymed = GetCompatibleTymed(format, data);
// If a TYMED was found, we can use the system DataObject
// to convert our value for us.
if (tymed != ComTypes.TYMED.TYMED_NULL)
= tymed;

// Set data on an empty DataObject instance
DataObject conv = new DataObject();
true, data);

// Now retrieve the data, using the COM interface.
// This will perform a managed to unmanaged conversion for us.
ComTypes.STGMEDIUM medium;
ref formatETC, out medium);
// Now set the data on our data object
((ComTypes.IDataObject)dataObject).SetData(ref formatETC, ref medium, true);
// On exceptions, release the medium
ReleaseStgMedium(ref medium);
// Since we couldn't determine a TYMED, this data
// is likely custom managed data, and won't be used
// by unmanaged code, so we'll use our custom marshaling
// implemented by our COM IDataObject extensions.

ComTypes.ComDataObjectExtensions.SetManagedData((ComTypes.IDataObject)dataObject, format, data);

There is a little bit of magic here. You'll see that I call the SetManagedData function at the end of SetDataEx as a last resort only. The rest of the code actually uses a temporary .NET DataObject instance to act as a converter. First I fill a FORMATETC structure for my desired format. Then I attempt to determine the TYMED based on the format name and data. The method GetCompatibleType is uninteresting, and is based on reflection of the .NET DataObject conversion capabilities. ("Compatible" means that it can be converted during a COM GetData call to the .NET DataObject.) It is slightly different for SWF and WPF, but is largely the same. Once I determine a compatible type, I set the data on the temporary .NET DataObject using its .NET SetData method, then call the COM GetData method, which will do marshaling according to the format and type of data. Then, since the marshaling is done, I can just set the data onto the real data object through the COM SetData method. Notice, only if I fail to determine a compatible TYMED do I do my own marshaling using SetManagedData.

Managed SetData Summary

Now that SetDataEx is in place, it can be used like the .NET SetData method. The only catch is that the data must be either serializable, or have an associated TypeConverter that supports conversion to/from a string. The GetData function will still work other than when the data's type is not serializable. In that case, you can call GetDataEx, which I won't show here. It basically confirms the stamp, then deserializes the data's type, then the data, and performs conversion through a TypeConverter if necessary.

The DragSourceHelper

I've discussed a lot about the heart of the problem and have provided a solid foundation to build a complete solution. However, there are some intricacies to proper implementation that I haven't addressed. We need to handle, as a drag source, the cases when the cursor is over a non-drag image enabled drop target, a drop target that doesn't set the drop description, and the Windows Taskbar (which just throws a curve ball for fun). To handle all this, I've implemented the DragSourceHelper, which is a static class that keeps some smart context to eliminate much of the repetitive client coding and to help you manage drag and drop with less hassle.

Default QueryContinueDrag Handler

I'll be honest up front. The QueryContinueDrag event seems pointless to me. I don't know when a drag source should ever have control over whether to continue a drag and drop operation. Having said that, there is one thing I could think of, that the system, for whatever reason, doesn't handle. That is the escape key. If the user presses escape during the drag and drop operation, most drag sources will just cancel the operation (or should, anyway). That is what my default handler does:

public static void DefaultQueryContinueDrag(QueryContinueDragEventArgs e)
if (e.EscapePressed)
= DragAction.Cancel;

Not much more to say here. This method belongs to my DragSourceHelper class and can be called directly from your QueryContinueDrag event handler, or I also expose a QueryContinueDragEventHandler method, so that you can assign it directly as the handler of your QueryContinueDrag event:

public static void DefaultQueryContinueDragHandler(object sender, QueryContinueDragEventArgs e)

There isn't anything more to say here. For my implementation, that is about all there is.

Default GiveFeedback Handler

The GiveFeedback event handler has a lot more to do than the QueryContinueDrag handler. This is because I have several cases I have to cover:

  • Drop targets that don't support drag images

Problem: If I assume all drop targets support drag images and don't make a special case, I'll end up showing them a plain arrow cursor, and the drop action will be completely unknown to the user.

Solution: For drop targets that don't support drag images, the drag image manager provides a piece of data in my data object that let's me know this is the case. The "IsShowingLayered" data format is a boolean value, true to indicate that the drop target is showing the drag image, false to indicate not. If the flag is on, I'll set UseDefaultCursors to false, then explicitly set the arrow cursor as my current mouse cursor. If it is not on, I'll set UseDefaultCursors to true, and I won't set the current cursor explicitly.

  • Drop targets that support drag images but don't set the drop description

Problem: These targets talk to the Shell's IDropTargetHelper implementation, but don't set the drop description. For these targets, we can provide a default drop description instead of the out-of-date default drag and drop cursors.

Solution: I can handle this by taking care to handle all drag and drop events properly. For one, during DragLeave, I can make sure to set my drop description as invalid, by using the DropImageType.Invalid type. That is what Windows Explorer does, too, so we are consistent. Next, through GiveFeedback, I can detect this invalid drop description, then look at the current drop effect, set by the drop target, and set a default description. That part is easy, the only thing you have to really worry about is the fact that drop targets that don't set the drop description won't invalidate them either! That means I need to track when I have a default drop description currently set. Then I can detect changes to the drop effect and change the current drop description. I'll do this with a flag that is associated to the IDataObject. Well, I lied. That isn't the only thing to worry about. You also need to detect when, after you set a default drop description, a drop target sets a drop description. This can be caused by the mouse leaving the drop target that doesn't set the drop description and entering a drop target that does. I detect this by implementing an IAdviseSink and listening to DataChanged events on the DataObject. I'll address that in the next section, Adding Support for AdviseSinks to the DataObject.

  • The Windows Taskbar

Problem: The Taskbar supports drag images, but doesn't set the drop description. The reason it is in a different category is because it is the most prominent place you'll notice that when you drag over it, from Windows Explorer, the no-smoking icon appears (drop effect None) but you won't see drag text. This is actually true for any drop target that supports drag images, but doesn't set a drop description and has a drop effect of None.

Solution: I can implement this by simply not invalidating the drag image after it has been rendered. That is, if I don't invalidate the drag image, then the drop description's icon will not refresh with the text after a pause. In order to implement this properly, I have to invalidate the drag image once after the drop description is set to None, and then not until it changes again.

The handler, after all of these cases are covered, looks like this:

public static void DefaultGiveFeedback(IDataObject data, GiveFeedbackEventArgs e)
// For drop targets that don't set the drop description, we'll
// set a default one. Drop targets that do set drop descriptions
// should set an invalid drop description during DragLeave.
bool setDefaultDropDesc = false;
bool isDefaultDropDesc = IsDropDescriptionDefault(data);
DropImageType currentType
= DropImageType.Invalid;
if (!IsDropDescriptionValid(data) || isDefaultDropDesc)
= GetDropImageType(data);
= true;

if (IsShowingLayered(data))
// The default drag source implementation uses drop descriptions,
// so we won't use default cursors.
e.UseDefaultCursors = false;
= Cursors.Arrow;
= true;

// We need to invalidate the drag image to refresh the drop description.
// This is tricky to implement correctly, but we try to mimic the Windows
// Explorer behavior. We internally use a flag to tell us to invalidate
// the drag image, so if that is set, we'll invalidate. Otherwise, we
// always invalidate if the drop description was set by the drop target,
// *or* if the current drop image is not None. So if we set a default
// drop description to anything but None, we'll always invalidate.
if (InvalidateRequired(data) || !isDefaultDropDesc || currentType != DropImageType.None)

// The invalidate required flag only lasts for one invalidation
SetInvalidateRequired(data, false);

// If the drop description is currently invalid, or if it is a default
// drop description already, we should check about re-setting it.
if (setDefaultDropDesc)
// Only change if the effect changed
if ((DropImageType)e.Effect != currentType)
if (e.Effect == DragDropEffects.Copy)
"Copy", "");
else if (e.Effect == DragDropEffects.Link)
"Link", "");
else if (e.Effect == DragDropEffects.Move)
"Move", "");
else if (e.Effect == DragDropEffects.None)
null, null);

// We can't invalidate now, because the drag image manager won't
// pick it up... so we set this flag to invalidate on the next
// GiveFeedback event.
SetInvalidateRequired(data, true);

You'll notice that I first detect if I will need to set the default description, and get the current drop image type. I don't set the default drop description at this point, because it doesn't make a difference. Calling InvalidateDragImage after setting the drop description here will not truly invalidate the drag image. Next, I determine if the drop target supports drag images via my IsShowingLayered helper function. If so, we won't use default cursors, else we will. Next, we determine if we should invalidate the drag image. Basically, if our flag is set from a previous call, or if the drop description was manually set by the drop target, or the drop description's drop image type is not None, then we'll invalidate, plus clear the flag to invalidate next time. Notice, that only when the None effect is applied do we possibly not invalidate the drag image.

Adding Support for AdviseSinks to the DataObject

In order for the GiveFeedback handler described above to work as expected, we have to detect when a drop target manually sets a drop description. There are a couple of ways to do this, but the most reliable is to listen to DataChanged events from the IDataObject. The COM IDataObject has a couple of methods that, until now, have been left as unsupported. They are DAdvise and DUnadvise. They are simply methods that enable you to pass an instance of IAdviseSink to listen for data events. The IAdviseSink interface covers advisory tasks for several system components, but for drag and drop, we just need to implement the OnDataChanged method:

private class AdviseSink : ComTypes.IAdviseSink
// The associated data object
private IDataObject data;

/// <summary>
/// Creates an AdviseSink associated to the specified data object.
/// </summary>
/// <param name="data">The data object.</param>
public AdviseSink(IDataObject data)
{ = data;

/// <summary>
/// Handles DataChanged events from a COM IDataObject.
/// </summary>
/// <param name="format">The data format that had a change.</param>
/// <param name="stgmedium">The data value.</param>
public void OnDataChange(ref ComTypes.FORMATETC format, ref ComTypes.STGMEDIUM stgmedium)
// We listen to DropDescription changes, so that we can unset the IsDefault
// drop description flag.
object odd = ComTypes.ComDataObjectExtensions.GetDropDescription((ComTypes.IDataObject)data);
if (odd != null)

#region Unsupported callbacks

public void OnClose()
throw new NotImplementedException();

public void OnRename(System.Runtime.InteropServices.ComTypes.IMoniker moniker)
throw new NotImplementedException();

public void OnSave()
throw new NotImplementedException();

public void OnViewChange(int aspect, int index)
throw new NotImplementedException();

#endregion // Unsupported callbacks

Because we only care about DropDescription changes, we will connect an instance of the AdviseSink class for notifications of that format only. The OnDataChange function is the callback from the IDataObject and simply verifies the drop description is accessible and then unsets the IsDefault flag for the data object. This tells the DragSourceHelper that this data object no longer has the default drop description.

The drop description flags I've mentioned, IsDefault and InvalidateRequired, are just a couple of internal flags, which I store in a Dictionary keyed on the IDataObject itself. That way I don't need to set internal-only data on the data object.

The Do-It-All Functions

The DragSourceHelper class isn't too complex, but it is more than I want to go into here. Most of it is just convenient do-it-all or do-it-in-less-lines-of-code functions. Having said that, there are a couple of functions I want to point out. You'll see in the examples, I use the DragSourceHelper.DoDragDrop function. The DoDragDrop function is a one-liner to call for the drag source, and handles all the internal drag and drop logic from the drag source side. The only thing you need to do is prepare a drag image, call DoDragDrop, and then handle the drag drop effect. Of course, for the cases that you need a little more granularity, there are several available functions and overrides to help you get what you need to get done without the hassle. Let's take a look at the DoDragDrop function (I included source for a couple of the called functions for reference):

public static DragDropEffects DoDragDrop(
Control control,
System.Drawing.Point cursorOffset,
DragDropEffects allowedEffects,
params KeyValuePair<string, object>[] data)
IDataObject dataObject
= RegisterDefaultDragSource(control, cursorOffset);
return DoDragDropInternal(control, dataObject, allowedEffects, data);

public static IDataObject RegisterDefaultDragSource(
Control control, System.Drawing.Point cursorOffset)
IDataObject data
= CreateDataObject(control, cursorOffset);
RegisterDefaultDragSource(control, data);
return data;

public static void RegisterDefaultDragSource(Control control, IDataObject data)
// Cache the drag source and the associated data object
DragSourceEntry entry = new DragSourceEntry(data);
if (!s_dataContext.ContainsKey(control))
s_dataContext.Add(control, entry);
= entry;

// We need to listen for drop description changes. If a drop target
// changes the drop description, we shouldn't provide a default one.
entry.adviseConnection = ComTypes.ComDataObjectExtensions.Advise(
new AdviseSink(data), DropDescriptionFormat, 0);

// Hook up the default drag source event handlers
control.GiveFeedback += new GiveFeedbackEventHandler(DefaultGiveFeedbackHandler);
+= new QueryContinueDragEventHandler(DefaultQueryContinueDragHandler);

private static DragDropEffects DoDragDropInternal(
Control control, IDataObject dataObject,
DragDropEffects allowedEffects,
<string, object>[] data)
// Set the data onto the data object.
if (data != null)
foreach (KeyValuePair<string, object> dataPair in data)
dataObject.SetDataEx(dataPair.Key, dataPair.Value);

return control.DoDragDrop(dataObject, allowedEffects);

What you'll see is that the DoDragDrop creates an instance of the DataObject, including registering it internally for default GiveFeedback and QueryContinueDrag event handlers and DataChange handler on the DropDescription format. It then adds any data you passed in, and calls the .NET DoDragDop, and then unregisters the event handlers before it returns the result drag drop effect.

This reduces the client code to a one-liner, as in the examples:

DragSourceHelper.DoDragDrop(button1, new Point(e.X, e.Y), DragDropEffects.Link | DragDropEffects.Copy,
new KeyValuePair<string, object>(DataFormats.Text, "Hello Drag and Drop!"),
new KeyValuePair<string, object>(DataFormats.Html, "<h4>Hello Drag and Drop!</h4>"));

In this example, you may have noticed, we are initializing the drag image from the button control itself, instead of a custom bitmap. Other overrides exist to help you achieve your most intriguing drag and drop experience, yet.


The last couple of weeks have gone deep into the implementation of a complete Shell-integrated drag and drop experience for your .NET apps. Whether you are working in WinForms or WPF, you can provide great user feedback during drag and drop operations. With WPF, this provides a great base for applications that are highly dynamic in the user experience. In WinForms, you can get that true integration that you've felt like .NET lacked.

I encourage you to use the source provided, although please keep in mind it is Microsoft copyrighted.

Source Code

All source for this article is provided below.

File Description
Complete drag and drop library as single CS file. Drop directly into your existing projects. Includes SWF and WPF implementations. No samples.
Complete set of projects for the drag and drop library. Includes samples.
The above link has an important bug fix, but if you need the original sources, you can download this one.


Comments (40)
  1. meta says:

    Awesome article-series.

  2. JDS says:

    Simply downloaded the project, buillt and ran.  When I try to drag I get this:

    “Unable to cast COM object of type ‘DragDropLib.DragDropHelper’ to interface type ‘DragDropLib.IDragSourceHelper2’. This operation failed because the QueryInterface call on the COM component for the interface with IID ‘{83E07D0D-0C5F-4163-BF1A-60B274051E40}’ failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).”

    I know next to nothing about COM so debugging this error is not something I have time to do, but would love to use the shell style drag and drop.  I had SDK 6.0A installed and upgraded to 6.1 with no luck.

  3. Garry Trinder says:

    Hey JDS. Thanks for your comment. Only Vista supports the IDragSourceHelper2 interface. You should be able to comment out or strip out a couple of lines to prevent the error, or better yet, write some code to intelligently decide whether to use the interface based on the current OS. Not a common thing to do in .NET, because it is supposed to be platform independent, but when you are using platform features, you’re bound to see errors like this.

    Good luck, let me know if you need further help.

  4. Flagada says:

    Thank you for these very useful articles!

    I’m currently working on a treeview with drag&drop features using code provided here. I would like to publish my work on CodeProject under GNU GPL. Is it allowed by Microsoft copyright (“I encourage you to use the source provided, although please keep in mind it is Microsoft copyrighted.”)?

    Thank you for advance.

  5. Garry Trinder says:

    You can put code on CodeProject that *uses* this code, but you must link to this blog post for the user to get the drag and drop code. I know it’s less than ideal for your article, but you cannot host or directly link to the code of this article. Thanks for checking on this.

  6. Joel says:

    Thank you very much for this fantastic library. Just one question. I wonder if it possible to decrease the opacity/transparency of the item that is dragged. I would like it to be more visible. I use the winforms library and I would really appreciate an answer 🙂

  7. Joel says:


    Is it possible to rewrite this code:

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






    Without using unsafe? I have tried but I don’t get the same transparency result.

  8. DaveT says:

    Great series of posts, but I am having difficulty do a FileDrop onto Windows Explorer. I am getting a COMException. The server threw an exception. (Exception from HRESULT: 0x80010105 (RPC_E_SERVERFAULT)).

    In the WPFSample I modified BorderMouseDown like this:

    string[] array = new string[1];

    array[0] = "C:\Users\Dave\Desktop\Foo.jpg";

    DragSourceHelper.DoDragDrop(border, e.GetPosition(border), DragDropEffects.Link | DragDropEffects.Copy,

                           new KeyValuePair<string, object>(DataFormats.Text, "Hello Drag and Drop!"),

                           new KeyValuePair<string, object>(DataFormats.FileDrop, array),

                           new KeyValuePair<string, object>(DataFormats.Html, "<h4>Hello Drag and Drop!</h4>"));

    I can drop onto IE and onto the drop target that accepts FileDrop without issue, but explorer blows things up.

    Any ideas on what the cause might be?

  9. DaveTrenaman says:

    Thank you for the excellent series of post. I am having trouble getting file drop to work. I am getting the following COM Exception (Exception from HRESULT: 0x80010105 (RPC_E_SERVERFAULT) when dropping into Explorer. I  have edited DragDropSample.xaml.cs to look like this

                     string[] array = new string[1];

                     array[0] = "C:\Users\Dave\Desktop\LL\Foo.jpg";

                       DragSourceHelper.DoDragDrop(border, e.GetPosition(border), DragDropEffects.Link | DragDropEffects.Copy,

                           new KeyValuePair<string, object>(DataFormats.Text, "Hello Drag and Drop!"),

                           new KeyValuePair<string, object>(DataFormats.FileDrop, array),

                           new KeyValuePair<string, object>(DataFormats.Html, "<h4>Hello Drag and Drop!</h4>"));

    I can drop onto the FielDrop pad without issue but when I try to drop into Explorer the exception happens.

    Any ideas as to what might be going wrong.



  10. Garry Trinder says:

    Hey Dave,

    I’m sorry, I’m not sure what is happening. I was able to reproduce the behavior, so if I get some time I’ll look into it further. Your code does work outside dropping into the Explorer Shell. I, for example, dropped a file into Visual Studio just fine. Because this drag and drop stuff does some things with data formats that the Shell uses internally, it is likely that by setting some, but not all, of the formats, that the Shell chokes when it doesn’t find something it expects. I’ll have to experiment to see if I can find out what it is looking for.


  11. Pete McQuain says:

    Hi and thanks for a great bit of code!

    I’m running into an issue that I think has to do with multi-threading. I’m using the code in an app that allows you to start more than one "main" window on multiple threads. Everything works as expected on the first window you do drag and drop operations on, but when you try to do a DND operation on one of the other windows, an E_NOTIMPL exception is thrown from SwfDataObjectExtensions.SetDragImage, when trying to create the new sourceHelper (line 87). It doesn’t matter on which window the DND operation is performed first, as it will always work, it’s the subsequent tries in other windows that fail. My entry point is marked [STAThread] and the new desktop threads are created STA also. This is a winforms app on .net 3.0.

    Any ideas?



  12. Martin Fenech says:

    I have an instance where I can’t get this code to work. From a wpf application I am launching a new window on another thread. If i try to drop something on the newly created window from the main app, the code  fails on line "dropHelper.DragEnter(windowHandle, (ComIDataObject)data, ref pt, (int)effect);" in method DragEnter in file WpfDropTargetHelperExtensions.cs. Any idea why?

  13. Phil says:

    Hmm, any luck working around the FileDrop error? I am seeing that as well.

  14. pjcast says:

    Still seeing this isse with dragging a FileDrop to disk. I attached Visual Studio to explorer.exe and did the drag. The error that gets throw/caught within explorer is:

    First-chance exception at 0x7cadacda in explorer.exe: 0xC0000005: Access violation reading location 0x00000000

    Though, I have no source of that to know what is null. So, I must try to compare the source of what the normal DoDragDrop does compared to what this extended version does (the normal one works OK, only no dragged image 🙁 ).

  15. Garry Trinder says:

    Well, I apologize for taking so long to get to all these comments, it seems my email notifications stopped coming in! I’ll try to address them as I find time.


  16. Garry Trinder says:

    Pete and Martin, please try adding the [ThreadStatic] attribute to the s_instance member of the DropTargetHelper class in the appropriate implementation (SWF or WPF) and see if that helps. It is a shot in the dark, but let me know if it helps. There may be other statics that should be ThreadStatic, too.

  17. Garry Trinder says:

    Phil, pjcast, and DaveT, I think I’ve found the problem. It looks like in DataObject.GetDataHere (which is called by GetData as well) I need to return DV_E_FORMATETC when the data format is not available. I will get a new version of the code up, but for now, you can add this to the end of GetDataHere instead of setting "medium = default(STGMEDIUM)":

    throw Marshal.GetExceptionForHR(DV_E_FORMATETC);

    Note that a better solution is to return the value DV_E_FORMATETC, but that means I need to fix GetData and GetDataHere to return int (HRESULT).

    Let me know if that works for you!

  18. pjcast says:

    Thanks! Adding throw Marshal.GetExceptionForHR(DV_E_FORMATETC); worked perfectly.

  19. pjcast says:

    One other quick thing I noticed (for some reason on one XP machine, the image drag is failing (probably related to a failing graphics hardware)), but there was a call to DeleteObject, which would also then fail. So, I modified my code locally to call that function, and it doesn’t appear to be defined within gdiplus.dll, but instead should be imported from: Gdi32.dll.

    Changing the import to that seems to fix that particular error. Can you confirm that this is the correct DLL to import from?

    Note, both PCs are running Windows XP SP3.

  20. Garry Trinder says:

    This code was never tested on XP, so I apologize for any bugs related to it. I do know that IDragSourceHelper2 is only supported as of Vista, but the code assumes it is there (it doesn’t catch exceptions, even).

    According to MSDN ( You are correct, Gdi32.dll is the one to import.

  21. Karin says:

    Hi, Great article!

    I have some issues developing with your help classes in a WPF application.

    The application is a desktop application, one UI thread, multiple controls on a single "desktop" background that consists of a Grid control.

    I have a grid where different areas are considered to be outside of the allowed drop bounds (but still being in the same grid). For that I use the DragOver and set the efffect to None. However this is not reflected in the preview image. If I change the description I can force the preview to be invalidated. But then there is a delay when I drag so it will take quite some time before the preview is updated.

    Is this the expected behaviour? Is the None effect only supported on DragEnter for some reason? Because dragging over another control will switch the icon directly.

    What if I have drop targets that does not call DropTargetHelper.Drop(e.Data, e.GetPosition(this), e.Effects)?

    Is that an issue? Because in my application I can not control all the parts that are placed on my Grid. If they receive a drop that has a preview set I got the effect of the drag image being visible on the screen after the drop.

    And in that case DropTargetHelper.Drop(e.Data, e.GetPosition(this), e.Effects) was not called nor DragLeave since the mouse was still on the Grid.

    Are there any risks with using you implementaion in a composite application? Mixing this drag and drop with pure standard drag and drop?

    As I try and cover my requirements I’m also getting two different MDA exceptions, DisconnectedContext was detected and ContextSwitchDeadlock was detected.

  22. Garry Trinder says:

    Hi Karin,

    Because you have a control that handles DragEnter and DragOver, but then allows another control to take over the current drag-drop operation, I suspect some issues could occur, although I’m afraid I’d just have to experiment to find the answer, because I don’t know for sure. I’d suggest trying to be more specific about which areas are drop targets, and make sure to call DragLeave when the drop operation is outside that area. That may require you doing some special handling of PreviewMouseMove events or something to detect when the drag-drop is still inside the bounds of your grid, but outside the bounds of your drag-drop target area.

    I don’t think any side effects will occur if DropTargetHelper.Drop isn’t called, but I’d try to make sure either DragLeave or Drop are called if you call DragEnter. It may not be necessary, but I think that would be the "correct" implementation.

    I haven’t seen the DisconnectedContext and ContextSwitchDeadlock exceptions. Are you running in STA mode? I think WPF requires it, but I’m not positive. From reading about DisconnectedContext, it sounds like you may be experiencing this during application shutdown? I’m just guessing, because it sounds like this most often occurs when the STA thread where the COM component exists is terminated, and then something like GC trieds to access it.


  23. Phil says:

    I’m seeing the same issue as Pete. Setting the [ThreadStatic] solves one issue it seems, but still the exception in the SetDragImage. (Also have to new s_instance in the functions and not globally in the class).

    What I see is strange though… is that this works, sort of. I open three windows (each STA, each running a Dispatcher message loop, each with their own window).

    If I drag in the 3rd window it works there. However, neither window 2 or 1 can drag an image. If I then close window 3 (also in the process am forcing GC – not sure if related or not), I can then drag in either other window – but not both. Closing the next window allows me to drag in the last open window. Any new opened windows also fail to drag until the last successfully dragged image window is closed.

    Any thoughts?

  24. Phil says:

    I actually was able to find a workaround, not sure how good it is, but it seems to work.

    In DropTargetHelper:


           internal static IDropTargetHelper s_instance = null;


           internal static DragDropHelper sDragDropHelper = null;

    And, in every method of that class:

               if (sDragDropHelper == null)

                   sDragDropHelper = new DragDropHelper();

               if (s_instance == null)

                   s_instance = (IDropTargetHelper)sDragDropHelper;

    And, at the bottom of DragLeave(IDataObject data) & Drop(IDataObject data, Point cursorOffset, DragDropEffects effect):

               //Clean up

               if (s_instance != null)


               s_instance = null;

               if (sDragDropHelper != null)


               sDragDropHelper = null;

    And in WpfDataObjectExtension SetDragImage(this IDataObject dataObject, Bitmap bitmap, Point cursorOffset):



                   //IDragSourceHelper sourceHelper = (IDragSourceHelper)new DragDropHelper();

                   if (DropTargetHelper.sDragDropHelper == null)

                       DropTargetHelper.sDragDropHelper = new DragDropHelper();

                   IDragSourceHelper sourceHelper = (IDragSourceHelper)DropTargetHelper.sDragDropHelper;



                       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(Exception ex)


                   //Log it

                   string message = string.Format("Error creating image for drag = {0}.", ex.GetBaseException());

                   SeekTech.Common.ClassLogger logger = new SeekTech.Common.ClassLogger(SeekTech.Common.LoggingLibrary.Unknown, "SeekTech.ConnectLink.Gui");

                   logger.CreateLogEntry(ex.GetBaseException(), "SetDragImage", message, message);

                   // We failed to initialize the drag image, so the DragDropHelper

                   // won’t be managing our memory. Release the HBITMAP we allocated.



    Basically, sharing and recreating the COM object seems to now let every window & thread do the drag image (also, dragging images between the Windows work).


  25. Phil says:

    Seems to be some instances where my fix doesn’t work… At some point one of my Windows can get into the state again where COM object fails (but newly created window’s work) . I Suspect the dragleave or drop method was not called by some control.

  26. Phil says:

    Heh, finally figured out how to reproduce the earlier issue I mentioned about the one computer who could not see the drag image… Apparently, the window’s setting "Show windows contents while dragging" set to false, causes:

    sourceHelper.InitializeFromBitmap(ref shdi, (ComIDataObject)dataObject);

    To throw a COMException:

    Error HRESULT E_FAIL has been returned from a call to a COM component.

    Though, seems like pretty much no program (except explorer when folder is set to view thumbnails) is able to show a dragged image.

  27. Min says:

    Hi, adam, I find the zip download link is broken. Could you fix it?

  28. Garry Trinder says:


  29. Garry Trinder says:

    Fixed, again. I’m hosting my files on SkyDrive. If anyone has more issues, please let me know. You can also try browsing my public folder on SkyDrive:

  30. Garry Trinder says:

    I have again updated the download links. I’m not sure why they go stale. Does anyone know why SkyDrive file links become unavailable?

  31. phil says:

    Not sure, but you could host this on codeplex, codeproject, or somewhere else more reliable. If on Codeplex, might be easier to access, fix bugs, communicate, etc. It is a pretty useful library you have created! 🙂

  32. Garry Trinder says:

    Thanks, I’m glad to see people finding it useful. I’ve been contacted by some of the WPF folks about it, so maybe we’ll see better support in the framework in the future. =)

    I found out why my download links weren’t working. It was because I was linking to the direct download links on SkyDrive, which are intentionally changed daily to prevent direct linking. It may sound bogus, but they need to make their money somewhere, so I’ve linked to the appropriate staging page where they get to show some ads, and those seem to be holding up.

  33. garby says:

    Hello adamroot,

    i have a problem using your code in Windows 7 64bit.

    If I drag out to the windows explorer the drag operation stops.

    Do you have any idea?



  34. Garry Trinder says:

    Garby, sorry but I haven’t tried this code on Windows 7 or on any x64 machine.

    My guess is it is a 64-bit problem. Since I didn’t have x64 at the time, I may have bugs in the 64-bit interop code. Try using corflags to set the 32bit flag on your .exe and see if you see the same behavior.


  35. garby says:

    Hi adamroot,

    my exe is running in 32bit mode anyway, because i use a 32bit native dll.

    Do you have any possibilities to try this in Windows 7?

    Thank you


  36. Gert says:

    Hi Adam.

    Great library. :thumbsup:

    I do have one small problem though… I keep getting an InvalidOperationException when dragging over other applications like Explorer.

    I’m running Windows 7 Home Premium 64bit. I have tried setting the CPU type of both the library and the sample to x86 but it didn’t help.

    I think it may be the same (or related) to garby’s issue.

    Do you have any ideas?



  37. Garry Trinder says:

    Garby and Gert,

    Unfortunately I don’t know what the issue is. I will certainly put this on my backlog, but at the moment, I just don’t have the bandwidth to spare on this.


  38. Gert says:

    Hi Adam.

    I did find, that if I change the DoDragDropInternal method to this :

               // Set the data onto the data object.

               if (data != null)


                   foreach (KeyValuePair<string, object> dataPair in data)

                       dataObject.SetDataEx(dataPair.Key, dataPair.Value);


               while (true)





                       return DragDrop.DoDragDrop(dragSource, dataObject, allowedEffects);


                   catch { }






    it works.

    When the exception occurs, the DND operation is simply restartet. Only sideeffect I’ve noticed is, that the Link, Move, Copy etc. icon, changes to XP-style instead of Win7 style when the exception occurs, but the DND operation continues.

    I know it’s not the "right" solution. Better to fix exceptions rather than circumventing them :o)

    This could potentially introduce a memoryleak in the program, so I would still appreciate it if you find the time to investigate this.


  39. Phil says:

    I also see the same error happen on Windows 7 (both 32 & 64 bit). Catching it in a remote debugger shows me an exception in

    DataObject.cs public int QueryGetData(ref FORMATETC format)

    Specifically, the enumeration through the storage list results in a exception about enumerating after a change in the list.

    So, what I’ve done – which seems to initially fix it… Is lock every usage of the storage list, and in one function change how things are removed from it:

           public void SetData(ref FORMATETC formatIn, ref STGMEDIUM medium, bool release)


               lock (mStorage)


                   // If the format exists in our storage, remove it prior to resetting it

                   for (int i = 0; i < mStorage.Count; ++i)


                       KeyValuePair<FORMATETC, STGMEDIUM> pair = mStorage[i];

                       if ((pair.Key.tymed & formatIn.tymed) > 0 && pair.Key.dwAspect == formatIn.dwAspect && pair.Key.cfFormat == formatIn.cfFormat)


                           STGMEDIUM releaseMedium = pair.Value;

                           ReleaseStgMedium(ref releaseMedium);







               // 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);

               lock (mStorage)




               RaiseDataChanged(ref addPair);


  40. hi,

    thanks you very much for this greate code.

    i need some help from your side

    I want to change cursor during drag drop from explorer/desktop to winform.

    whenever we drag and drop file then check if file is valid or not, if it is not valid then display custom cursor and if valid then display another custom cursor.

    please let me know how to do that. please give me an example.

    thanks in advance

Comments are closed.

Skip to main content