Creating something from nothing - and knowing it [Developer-friendly virtual file implementation for .NET refined!]
A couple of weeks ago I wrote about VirtualFileDataObject, my developer-friendly virtual file implementation for .NET and WPF. I followed that up by adding support for asynchronous behavior to improve the user experience during long-running operations. Last week, these posts got a shout-out from blogging legend Raymond Chen, whose work provided the inspiration for the project. [Best week ever! :) ] Now it's time for one last tweak to wrap things up!
Recall that the Windows APIs for drag-and-drop and clipboard operations deal with objects that implement the IDataObject COM interface. And while the native DoDragDrop function tries to provide a mechanism for the source and target to communicate (via the pdwEffect
parameter and the function's return value), there's nothing similar available for the SetClipboardData function. And once you enable asynchronous drag-and-drop, it's clear that the return value of DoDragDrop
isn't going to work, either, because the call returns immediately (before the operation has completed). So it seems like there must be some other way for the source and target to share information...
Sure enough, there are some shell clipboard formats specifically for enabling communication between the source and target! For the purposes of this sample, we're interested in using two of them:
- CFSTR_PREFERREDDROPEFFECT - What kind of operation (copy/move/link) the source would like to happen
- CFSTR_PERFORMEDDROPEFFECT - What kind of operation (copy/move/link) actually did happen
And here's another which is relevant enough that I wrote code to support it, though I'll only mention it once more here:
- CFSTR_PASTESUCCEEDED - Indication that a paste succeeded and what kind of operation (copy/move/link) it did
Armed with that knowledge, let's tweak the sample application so one of the scenarios does a move/cut instead of a copy:
The first tweak to the code modifies our helper function to accept a DragDropEffects
parameter so the caller can indicate its copy/move preference with the new PreferredDropEffect
property in the clipboard scenario:
private static void DoDragDropOrClipboardSetDataObject(MouseButton button, DependencyObject dragSource, VirtualFileDataObject virtualFileDataObject, DragDropEffects allowedEffects) { try { if (button == MouseButton.Left) { // Left button is used to start a drag/drop operation VirtualFileDataObject.DoDragDrop(dragSource, virtualFileDataObject, allowedEffects); } else if (button == MouseButton.Right) { // Right button is used to copy to the clipboard // Communicate the preferred behavior to the destination virtualFileDataObject.PreferredDropEffect = allowedEffects; Clipboard.SetDataObject(virtualFileDataObject); } } catch (COMException) { // Failure; no way to recover } }
Then we can tweak the VirtualFileDataObject
constructor to pass a custom "end" action that uses the new PerformedDropEffect
property to find out what action took place. If the target performed a move (or a cut with the clipboard), then we'll hide the corresponding "button" to reflect that fact:
private void VirtualFile_MouseButtonDown(object sender, MouseButtonEventArgs e) { var virtualFileDataObject = new VirtualFileDataObject( null, (vfdo) => { if (DragDropEffects.Move == vfdo.PerformedDropEffect) { // Hide the element that was moved (or cut) // BeginInvoke ensures UI operations happen on the right thread Dispatcher.BeginInvoke((Action)(() => VirtualFile.Visibility = Visibility.Hidden)); } }); // Provide a virtual file (generated on demand) containing the letters 'a'-'z' ... DoDragDropOrClipboardSetDataObject(e.ChangedButton, TextUrl, virtualFileDataObject, DragDropEffects.Move); }
Please note that the begin/end actions are now of type Action<VirtualFileDataObject>
. The new parameter is always a reference to the active VirtualFileDataObject
instance and is provided to make it easy to use anonymous methods like you see above.
Aside: Otherwise you'd need to capture theVirtualFileDataObject
instance so you could pass it to the action being provided to that same instance's constructor! (Catch-22 much?) While there may be a clever way to do this without making the begin/end actions properties of the class (which I didn't want to do because that wouldn't ensure they're invariant), passing theVirtualFileDataObject
in this manner is both easy and obvious.
Further aside: As a consequence of the changes to support these new properties,VirtualFileDataObject
now implements the IDataObject::SetData method forHGLOBAL
-style data. So if there's some otherCFSTR_
property that's relevant to your scenario, you can query it off theVirtualFileDataObject
instance in much the same manner!
[Click here to download the complete code for VirtualFileDataObject and the sample application.]
And that's pretty much all there is to it! With the addition of PreferredDropEffect
and PerformedDropEffect
(and PasteSucceeded
, though it's not demonstrated above), VirtualFileDataObject
makes it easy for your application to provide a seamless virtual file drag/drop experience with all the sophisticated nuances users expect from a polished Windows application.
Enjoy!