Copy, Paste, Drag & Drop

Copy/Paste and Drag & Drop operations are similar and are handled by Windows in the same way. The result of a Drag & Drop operation is the same as the result of a Copy/Paste operation (if it is 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 appear. For drag and drop, an end user can use modifier keys like CTRL to change the kind of operation.

If the ShellItem instance represents a physical file item, its default implementation will support Copy/Paste operations. In fact, because these are default operations, the corresponding menu items will be available automatically.

For virtual Shell items, you will be able to use the following properties to prevent certain behaviors:

  • CanLink: a shortcut (.lnk file) may 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 information). By default, for virtual items, this is not defined. If you enable copy, you also must override ShellItem's GetContent() method or the end user will see an error message dialog box.

If you want to handle Drag & Drop operations, you can override the OnDragDropTarget method of the ShellFolderServer class (for global handling) or the ShellItem class (for a particular item or items), like this:

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

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

On-Demand Data Object

It is possible to implement data objects (used notably in Copy/Paste and Drag & Drop operations) using an on-demand "pull" model as opposed to a static "push" model. Compared 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. This limits their ability to be integrated with some third-party applications.

The workflow is described here:


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 (e.g., Explorer, hosted Common Dialogs, Microsoft Office Outlook). This has the following advantages:

  • For the client application, the target, it changes nothing from the "push" model.
It's especially interesting when exchanging a 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. This mode is particularly well suited for the CFSTR_FILECONTENTS official Shell format.

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

protected override void OnGetOnDemandDataObjectEvent(object sender, GetOnDemandDataObjectEventArgs e)
{
       // By default, DataObject property is null.
       // Assigning it to an instance of an object instructs CBFS Shell to use this object 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 an equivalent to byte array in CBFS Shell 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 and we are just waiting for the MediumGet event.
       odo.AddFormat(format);
       base.OnGetOnDemandDataObjectEvent(sender, e);
}

Now, we will use a tool named DragDropViewer, which available here https://github.com/callbacktechnologies/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:


As you can see, the Data Object contains the usual formats (plus a CBFS Shell internal one named "ShellBoost.Core.Cookie"), as well as the one we just added.

Let's show a second example. Example 2: Just replace the above code with the following code in the same SimpleFolder.cs file:

protected override void OnGetOnDemandDataObjectEvent(object sender, GetOnDemandDataObjectEventArgs e)
{
       // By default, DataObject property is null.
       // Assigning it to an instance of an object instructs CBFS Shell to use this object instead of the usual methods.
       var odo = new OnDemandDataObject();
       e.DataObject = odo;
 
       // TYMED needs to use System.Runtime.InteropServices.ComTypes
       // Here, we add a simple format. TYMED_ISTREAM is an 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 and we're just waiting for the MediumGet event.
       odo.AddFormat(format);
       base.OnGetOnDemandDataObjectEvent(sender, e);
}

The code is similar; however, in this case, we use a .NET Stream that will be called only when the data is requested.

Let's go for a third example with Microsoft Outlook as a target. We will continue to use the Overview sample for this example. Example 3: Let's 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;
             ...
       }
       ...
}

Next, let's 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:


Now, 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":


Click OK and two "virtual files" will be added as two attachments to the message:


In the previous example, we let the OnDemandFileDescriptorsDataFormat class automatically use the selected items. But you may opt for a custom list of virtual files using the combined CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR formats. For that, you need to create a descendant of 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");
       }
}

Copyright (c) 2022 Callback Technologies, Inc. - All rights reserved.
CBFS Shell 2022 .NET Edition - Version 22.0 [Build 8172]