ShellBoost

Copy, Paste, Drag, Drop support

Copy and Paste and Drag and Drop operations are very similar and handled the same way by Windows. The result of a drag and drop operation is the same as the result of a copy and paste operation (if it’s not cancelled), the dropped object is transferred from the source and pasted to the destination.

Both operations can support shortcuts. If the “Paste” menu item is available and the copied object supports shortcuts, then a “Paste as Shortcut” menu item will be shown. For drag and drop, the end-user can use modifier keys like CTRL to change the operation effect.

If the ShellItem instance represents a physical file item, its default implementation will support copy paste operations. In fact, you won’t even be able to prevent these operations, because the corresponding menu items will be available anyway.

For virtual shell items, you will be able to use the various following properties:

CanLink: a shortcut (.lnk file) can be created for the item. The default value is true.

CanCopy: the item can be copied. The default value is false.

CanMove: the item can be moved. The default value is false.

IsDropTarget: the item is a drop target. The default value is false.

When copy occurs and it’s not a shortcut, the data that is copied is the ShellItem’s content (check the Item Content chapter for more on this). By default, for virtual items, it’s not defined so if you enable copy, you must also override ShellItem’s GetContent() method or the end-user will see an error message dialog box.

If you want to handle drag and drop operation, you can override the ShellFolderServer class (for global handling) or the ShellItem class OnDragDropTarget method, like this:

// handle drag & drop
protected override void OnDragDropTarget(DragDropTargetEventArgs e)
{
    e.Effect = DragDropEffects.Copy; // tell the system you accept drops
    // TODO: implement what is a drop on your namespace extension
}

The Cloud Folder sample and the Physical Overview sample demonstrate Drag & Drop and Copy / Paste operations support.

On-Demand Data Object

Starting with ShellBoost version 1.7.0.0, it’s possible to implement data objects (used notably in Copy / Paste and Drag & Drop) using an on-demand “pull” model as opposed to a static “push” model. As a comparison with existing tools and technologies, the “push” model is the only model currently supported by WinForms and WPF UI frameworks with their respective DataObject classes which limits their abilities to integrate with some third party applications.

The workflow is described here:

On-Demand Data Object - Picture 87

In this mode, the Data Object source (your code in the .NET server) is ready and waits for requests coming from the Data Object target (Explorer, hosted Common Dialogs, Microsoft Office Outlook, etc.). This has the following advantages:

For the client application, the target, it changes nothing from the “push” model.

It’s especially interesting when exchanging large amount of data/memory. In this mode, data is streamed from the source to the target, instead of being copied as large memory blobs of data in source, client, and technical proxies all along the execution path. It’s particularly suited for the CFSTR_FILECONTENTS official shell format.

Let’s show a first example. Just add the following code to the Overview sample, in SimpleFolder.cs file:

protected override void OnGetOnDemandDataObjectEvent(object sender, GetOnDemandDataObjectEventArgs e)
{
       // by default, DataObject property is null.
       // defining it instructs ShellBoost to use this instead of the usual methods.
       var odo = new OnDemandDataObject();
       e.DataObject = odo;
 
       // TYMED needs using System.Runtime.InteropServices.ComTypes
       // here, we add a simple format. TYMED_HGLOBAL is a moral equivalent to byte array in ShellBoost context.
       var format = new OnDemandDataFormat("MyCoolFormat", TYMED.TYMED_HGLOBAL);
 
       // respond to the client asking for this format's data
       format.MediumGet += (s2, e2) =>
       {
             // send back an array of bytes, here we just send an int32 (4 bytes).
             e2.Medium = new OnDemandDataMedium(BitConverter.GetBytes(12345678));
       };
 
       // add the format. At that moment in time, no data was added, we're just waiting for the MediumGet event.
       odo.AddFormat(format);
       base.OnGetOnDemandDataObjectEvent(sender, e);
}

Now, we will use a tool named DragDropViewer, available here https://github.com/aelyo-softworks/ShellBoost-Tools/tree/main/DragDropViewer that allows us to dump what’s in the clipboard (copied) and what’s being dragged over. If you navigate to the Overview sample and drag an item from the namespace extension to the tool’s surface you should see this:

On-Demand Data Object - Picture 88

As you see the Data Object contains the usual formats (plus a ShellBoost internal one named “ShellBoost.Core.Cookie”), plus the one we’ve just added.

Let’s show a second example. Just replace the above code by the following one in the same SimpleFolder.cs file:

protected override void OnGetOnDemandDataObjectEvent(object sender, GetOnDemandDataObjectEventArgs e)
{
       // by default, DataObject property is null.
       // defining it instructs ShellBoost to use this instead of the usual methods.
       var odo = new OnDemandDataObject();
       e.DataObject = odo;
 
       // TYMED needs using System.Runtime.InteropServices.ComTypes
       // here, we add a simple format. TYMED_ISTREAM is a moral equivalent to a .NET Stream
       var format = new OnDemandDataFormat("MyCoolFile", TYMED.TYMED_ISTREAM);
 
       // respond to the client asking for this format's data
       format.MediumGet += (s2, e2) =>
       {
             // send back a (file) stream, used only when asked for
             // it could also be a network stream
             e2.Medium = new OnDemandDataMedium(File.OpenRead(@"c:\somepath\somebigfile.ext"));
       };
 
       // add the format. At that moment in time, no data was added, we're just waiting for the MediumGet event.
       odo.AddFormat(format);
       base.OnGetOnDemandDataObjectEvent(sender, e);
}

The code is similar but, in this case, we use a .NET Stream that will only be called when (if) the data is requested.

Let’s go for a third example with Microsoft Office Outlook as a target. We’ll still be using the Overview sample. Just add the CanCopy = true statement in the constructor of the SimpleItem class in the SimpleItem.cs file:

public class SimpleItem : ShellItem
{
       public SimpleItem(ShellFolder parent, string text) : base(parent, new StringKeyShellItemId(text))
       {
             ...
             CanCopy = true;
             ...
       }
       ...
}

And add the following to the SimpleFolder.cs file.

protected override void OnGetOnDemandDataObjectEvent(object sender, GetOnDemandDataObjectEventArgs e)
{
       var odo = new OnDemandDataObject();
       e.DataObject = odo;
 
       // add CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR automatically using the current selected items
       odo.AddFormat(new OnDemandFileDescriptorsDataFormat(true));
       base.OnGetOnDemandDataObjectEvent(sender, e);
}

Now, go to the namespace extension, select two items, and click on the context menu’s “Copy” command:

On-Demand Data Object - Picture 116

If you go to Outlook and open a new Message window and place the cursor in the message body. Use the “Paste Special” command and choose “Files”:

On-Demand Data Object - Picture 117

Click OK and the two “virtual files” have been added as two attached files to the mail:

On-Demand Data Object - Picture 118

In previous example, we’ve let the OnDemandFileDescriptorsDataFormat class automatically use the selected items, but you can opt for a custom list of virtual files using the combined CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR formats. For that you must derive from the OnDemandFileDescriptorsDataFormat class and override the GetDescriptors method:

private class MyCustomListOfFilesFormat : OnDemandFileDescriptorsDataFormat
{
       public MyCustomListOfFilesFormat()
             : base(false) // don't add descriptors from the selected items
       {
       }
 
       protected override IEnumerable<FileStreamDescriptor> GetDescriptors(OnDemandDataObject dataObject)
       {
             yield return FileStreamDescriptor.FromFilePath(@"c:\SomePath\SomeFile1.ext");
             yield return FileStreamDescriptor.FromFilePath(@"c:\SomeOtherPath\SomeOtherFile.ext");
       }
}