[API Proposal]: FileGroup data transfer
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
### Background and motivation
The proposed API allows customers to drag, drop, copy and paste virtual files. Currently tasks like pasting files from remote desktop or dropping attachments from Outlook requires reflection to access the files (or rewrite the whole OLE infrastructure for data transfer).
PR #7738
### API Proposal
```diff
namespace System.Windows;
public static partial class Clipboard
{
public static void Clear();
public static bool ContainsAudio();
public static bool ContainsData(string format);
public static bool ContainsFileDropList();
+ public static bool ContainsFileGroup();
public static bool ContainsImage();
public static bool ContainsText();
public static bool ContainsText(TextDataFormat format);
public static void Flush();
public static Stream GetAudioStream();
public static object GetData(string format);
public static IDataObject GetDataObject();
public static StringCollection GetFileDropList();
+ public static FileGroup GetFileGroup();
public static BitmapSource GetImage();
public static string GetText();
public static string GetText(TextDataFormat format);
public static bool IsCurrent(IDataObject data);
public static void SetAudio(byte[] audioBytes);
public static void SetAudio(Stream audioStream);
public static void SetData(string format, object data);
public static void SetDataObject(object data);
public static void SetDataObject(object data, bool copy);
public static void SetFileDropList(StringCollection fileDropList);
+ public static void SetFileGroup(FileGroup group);
public static void SetImage(BitmapSource image);
public static void SetText(string text);
public static void SetText(string text, TextDataFormat format);
}
+ public partial interface IDataObjectWithIndex : IDataObject
+ {
+ object GetData(string format, bool autoConvert, int index);
+ bool GetDataPresent(string format, bool autoConvert, int index);
+ void SetData(string format, object data, bool autoConvert, int index);
+ }
- public sealed partial class DataObject : ComTypes.IDataObject, IDataObject
+ public sealed partial class DataObject : ComTypes.IDataObject, IDataObjectWithIndex, IDataObject
{
public static readonly RoutedEvent CopyingEvent;
public static readonly RoutedEvent PastingEvent;
public static readonly RoutedEvent SettingDataEvent;
public DataObject();
public DataObject(object data);
public DataObject(string format, object data);
public DataObject(string format, object data, bool autoConvert);
public DataObject(Type format, object data);
public static void AddCopyingHandler(DependencyObject element, DataObjectCopyingEventHandler handler);
public static void AddPastingHandler(DependencyObject element, DataObjectPastingEventHandler handler);
public static void AddSettingDataHandler(DependencyObject element, DataObjectSettingDataEventHandler handler);
public bool ContainsAudio();
public bool ContainsFileDropList();
+ public bool ContainsFileGroup();
public bool ContainsImage();
public bool ContainsText();
public bool ContainsText(TextDataFormat format);
public Stream GetAudioStream();
public object GetData(string format);
public object GetData(string format, bool autoConvert);
+ public object GetData(string format, bool autoConvert, int index);
public object GetData(Type format);
public bool GetDataPresent(string format);
public bool GetDataPresent(string format, bool autoConvert);
+ public bool GetDataPresent(string format, bool autoConvert, int index);
public bool GetDataPresent(Type format);
public StringCollection GetFileDropList();
+ public FileGroup GetFileGroup();
public string[] GetFormats();
public string[] GetFormats(bool autoConvert);
public BitmapSource GetImage();
public string GetText();
public string GetText(TextDataFormat format);
public static void RemoveCopyingHandler(DependencyObject element, DataObjectCopyingEventHandler handler);
public static void RemovePastingHandler(DependencyObject element, DataObjectPastingEventHandler handler);
public static void RemoveSettingDataHandler(DependencyObject element, DataObjectSettingDataEventHandler handler);
public void SetAudio(byte[] audioBytes);
public void SetAudio(Stream audioStream);
public void SetData(object data);
public void SetData(string format, object data);
public void SetData(string format, object data, bool autoConvert);
+ public void SetData(string format, object data, bool autoConvert, int index);
public void SetData(Type format, object data);
public void SetFileDropList(StringCollection fileDropList);
+ public void SetFileGroup(FileGroup group);
public void SetImage(BitmapSource image);
public void SetText(string textData);
public void SetText(string textData, TextDataFormat format);
int ComTypes.IDataObject.DAdvise(ref ComTypes.FORMATETC pFormatetc, ComTypes.ADVF advf, ComTypes.IAdviseSink pAdvSink, out int pdwConnection);
void ComTypes.IDataObject.DUnadvise(int dwConnection);
int ComTypes.IDataObject.EnumDAdvise(out ComTypes.IEnumSTATDATA enumAdvise);
ComTypes.IEnumFORMATETC ComTypes.IDataObject.EnumFormatEtc(ComTypes.DATADIR dwDirection);
int ComTypes.IDataObject.GetCanonicalFormatEtc(ref ComTypes.FORMATETC pformatetcIn, out ComTypes.FORMATETC pformatetcOut);
void ComTypes.IDataObject.GetData(ref ComTypes.FORMATETC formatetc, out ComTypes.STGMEDIUM medium);
void ComTypes.IDataObject.GetDataHere(ref ComTypes.FORMATETC formatetc, ref ComTypes.STGMEDIUM medium);
int ComTypes.IDataObject.QueryGetData(ref ComTypes.FORMATETC formatetc);
void ComTypes.IDataObject.SetData(ref ComTypes.FORMATETC pFormatetcIn, ref ComTypes.STGMEDIUM pmedium, bool fRelease);
}
public static partial class DataFormats
{
public static readonly string Bitmap;
public static readonly string CommaSeparatedValue;
public static readonly string Dib;
public static readonly string Dif;
public static readonly string EnhancedMetafile;
public static readonly string FileDrop;
public static readonly string Html;
public static readonly string Locale;
public static readonly string MetafilePicture;
public static readonly string OemText;
public static readonly string Palette;
public static readonly string PenData;
public static readonly string Riff;
public static readonly string Rtf;
public static readonly string Serializable;
public static readonly string StringFormat;
public static readonly string SymbolicLink;
public static readonly string Text;
public static readonly string Tiff;
public static readonly string UnicodeText;
public static readonly string WaveAudio;
public static readonly string Xaml;
public static readonly string XamlPackage;
+ public static readonly string FileContents;
+ public static readonly string FileGroupDescriptor;
public static DataFormat GetDataFormat(int id);
public static DataFormat GetDataFormat(string format);
}
+ public partial class FileDescriptor
+ {
+ public FileDescriptor(string filename);
+ public Guid? Clsid { get; set; }
+ public DateTime? CreationTime { get; set; }
+ public FileAttributes? FileAttributes { get; set; }
+ public string FileName { get; }
+ public long? FileSize { get; set; }
+ public static FileDescriptor FromFile(string path);
+ public Int32Rect? Icon { get; set; }
+ public bool IsDirectory { get; }
+ public DateTime? LastAccessTime { get; set; }
+ public DateTime? LastWriteTime { get; set; }
+ }
+ public partial class FileGroup : IReadOnlyList>
+ {
+ public FileGroup();
+ public int Count { get; }
+ public bool IsReadOnly { get; }
+ public void Add(string filename, byte[] data);
+ public void Add(string filename, Stream stream);
+ public void Add(FileDescriptor descriptor, Stream stream);
+ public IReadOnlyList FileDescriptors { get; }
+ public IEnumerator> GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator();
+ public Stream GetFileContents(FileDescriptor descriptor);
+ public Stream GetFileContents(int index);
+ KeyValuePair IReadOnlyList>.this[int index] { get; }
+ }
```
`FileDescriptor` corresponds to [FILEDESCRIPTORW](https://learn.microsoft.com/en-us/windows/win32/api/shlobj_core/ns-shlobj_core-filedescriptorw).
### API Usage
Copy/Drag:
```C#
FileGroup group = new FileGroup();
group.Add("E.txt", Encoding.UTF8.GetBytes("EHEHEHE"));
group.Add("F.txt", Encoding.UTF8.GetBytes("FFFFLUFF"));
// Copy virtual files into clipboard
Clipboard.SetFileGroup(group);
// Start drag operation
DataObject dataObject = new DataObject();
dataObject.SetFileGroup(group);
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.All);
```
Paste/Drop:
```C#
FileGroup group;
// From clipboard
if (Clipboard.ContainsFileGroup())
group = Clipboard.GetFileGroup();
// From drop handler
DataObject dataObject = (DataObject)e.Data;
if (dataObject.ContainsFileGroup())
group = dataObject.GetFileGroup();
// Save virtual files to the current directory
foreach (var file in group)
{
if (file.Key.IsDirectory)
Directory.CreateDirectory(file.Key.FileName);
else
using (FileStream stream = File.Create(file.Key.FileName))
file.Value.CopyTo(stream);
}
```
Low-level set without FileGroup:
```C#
DataObject dataObject = new DataObject();
dataObject.SetData(DataFormats.FileContents, Encoding.UTF8.GetBytes("AHAHA"), autoConvert: false, index: 0);
dataObject.SetData(DataFormats.FileContents, Encoding.UTF8.GetBytes("BAHAHA"), autoConvert: false, index: 1);
dataObject.SetData(DataFormats.FileGroupDescriptor, new[] { new FileDescriptor("A.txt"), new FileDescriptor("B.txt") });
```
Low-level get without FileGroup:
```C#
if (dataObject.GetDataPresent(DataFormats.FileGroupDescriptor))
{
var descriptors = dataObject.GetData(DataFormats.FileGroupDescriptor) as FileDescriptor[];
for (int i = 0; i < descriptors.Length; i++)
{
Stream contents = dataObject.GetData(DataFormatas.FileContents, autoConvert: false, index: i) as Stream;
Console.WriteLine($"{descriptor[i].FileName} {contents.Length} bytes");
}
}
```
### Alternative Designs
Only the get/set methods (i.e. `IDataObjectWithIndex`) are critical to enable developers to implement the functionality, the rest is for convenience.
To consider:
* How much should `FileGroup` be a full-fledged collection (supporting removal, CopyTo etc.)
* Whether `FileGroup` should contain any convenience methods for adding physical files and directories. This should have an option to add a whole subdirectory, as well as creating an internal stream wrapper that wouldn't open the file until it is actually requested by the drop/clipboard target. Currently these can be implemented by customers.
* Whether there should be a separate type instead of `KeyValuePair`. This would allow delaying requesting the stream until needed by the application (currently it is requested on per-iteration basis in enumerator).
Winforms does not have this functionality.
### Risks
This proposal shouldn't contain any breaking changes. `DataObject` now implements `IDataObjectWithIndex`, but direct implementation of `IDataObject` has been preserved.
Contributor guide
Assessment
This issue has not been assessed yet.