I want to share a solution to the problem related to using drag-n-drop in my Outlook 2010 add-in. The task was as follows: we needed to implement a UserControl that would accept MailItem or Selection by using the dragging functions, or more precisely Drag-and-Drop. The task turned out to be pretty simple, and we seemed to get everything working, but… there appeared one “BUT”!
The crux of the problem was that after dragging a MailItem onto our control, Outlook stopped reacting to SelectionChange, i.e. when an Item was selected its contents did not get refreshed in the Reading Pane, moreover each click increased SelectionCount. (This problem was first posted by Andrei Smolin on the Outlook for Developers forum, see Outlook 2010 issue. Drag-n-drop onto a custom task pane causes Selection.Count to grow.)
The problem is there and we have to resolve it. On the one hand, we have the source of drag-n-drop – it is a MailItem, and it is nothing other than a COM object, which is very interesting by itself. My rich experience with COM objects, especially with MSO, tells me they need to be released, but immediately a question arises: Does the Drag&Drop mechanism release them? We’ll try to figure this out. Let us look at the issue from another angle – from the angle of our form that accepts the MailItem object. But we got stuck with the .NET Framework straight away. Well, we’ll have to venture deeper into its maze and decipher how it all works.
Some theory. Windows API and drag and drop
For any window to work with drag-n-drop, it must be registered in the operating system using the RegisterDragDrop function that accepts two parameters: the first one is a window handle and the other – an object that realizes the OLE interface IDropTarget.
Let’s discuss this interface. IDropTarget has four methods:
- OnDragEnter (object pDataObj, int grfKeyState, long pt, ref int dwEffect) – occurs only once when an item being dragged gets into the area of a window registered as drag-n-drop;
- OnDragOver (int grfKeyState, long pt, ref int pdwEffect) – occurs continuously when an object is being dragged over a window registered as drag-n-drop;
- OnDragDrop (object pDataObj, int grfKeyState, long pt, ref int dwEffect) – occurs only once when an item being dragged is dropped in the area of a window registered as drag-n-drop;
- OnDragLeave () – occurs only once when an item being dragged leaves the area of a window registered as drag-n-drop.
Please pay attention to the fact that the system passes an object-source only for TWO of these events: for OnDragEnter and OnDragDrop.
The Control class of the System.Windows.Forms namespace has four analogous events (DragEnter, DragOver, DragDrop and DragLeave). What is interesting is that the object-source is passed in THREE events using the DragEventArgs parameter. All is clear with DragEnter and DragDrop – the object-source is passed there by the system via the OLE interface IDropTarget. But how does it get into the DargOver event? Let’s assume that after occurrence of the OnDragEnter event, it is saved into some local variable. If so, and if it is a COM object, then it must be released after usage.
I have done further research on this problem in the company of the NET Reflector. How does it work? In the System.Windows.Forms assembly, the Control class has the AllowDrop property; when it is set, a system drag-n-drop is registered or unregistered for each instance of this class.
During registration, the RegisterDragDrop function is called and, as described above, the handle of this control as well as the object implementing the OLE interface IDropTarget is passed to this function (please do not confuse it with the System.Windows.Forms.IDropTarget interface). An instance of the internal class DropTarget acts in the capacity of the mentioned object. Having a closer look at the implementation of this class I noticed (as I suspected from the very beginning!) that its developers forgot or omitted the fact that COM objects can also be dragged-and-dropped with all the ensuing consequences.
Well, it can’t be helped, we need to fix bugs produced by our counterparts. All in all, we had to slightly rewrite this class, include it in the project separately and give up the idea of using the standard AllowDrop property.
I created a simple shared add-in (see the download link below) for Outlook with one custom task pane onto which we are going to drag messages.
Build the setup project, install the add-in and start Outlook. Try dropping a couple of emails onto the task pane and pay attention that the Reading Pane reacts to your selecting this or that Outlook item as expected. Now tick the checkbox and try doing the same again. When you find that the Reading pane doesn’t refresh, try to open an email – beware, this may open a bunch of emails, all those you selected earlier.