views:

126

answers:

1

I have a C# WPF app that allows users to import files by dragging them in from Windows Explorer and dropping them on the main app window.

It works fine when dragging files from physical disks, but when dragging files from a connected device like an iPhone or camera connected via USB, I don't recognise any of the data formats returned by dragEventArgs.Data.GetFormats() in the window's Drop handler.

Anyone care to share some tips or point me at a good example or walkthrough of how to read/import files from a 'virtual' filesystem in C#/.NET in this way?

Thanks,

Dylan

+2  A: 

Getting the filenames

Getting the filenames is easy. Just call:

dragEventArgs.Data.GetData("FileGroupDescriptorW")

this will return a MemoryStream that contains a FILEGROUPDESCRIPTORA structure. This can be parsed to get the filenames. Here and here are links to projects on CodeProject that show you two different ways to parse FILEGROUPDESCRIPTORA in C#, so I won't go into detail here. I would probably use the technique described in the first project.

Getting the actual data

To get the actual data, you use the FileContents format. Unfortunately you either have to use reflection to access private methods, or write some COM interop yourself. The problem is that to get the data you must call the System.Runtime.InteropServices.ComTypes.IDataObject interface with a FORMATETC structure that has lindex set to the item index. Unfortunately System.Windows.DataObject's implementation always calls it with lindex=-1.

The easiest solution is probably to use reflection to call private members of WPF's DataObject. Be forewarned, however, that this may break your code in future NET Framework versions. If that is completely unacceptable, your other option is to call the RegisterDragDrop function in ole32.dll to register a custom IOleDropTarget, then talk directly to the COM IDataObject that is passed in. This is not terribly difficult, but the reflection solution is much easier and will probably work for many versions of NET Framework, so that is what I will focus on.

Here is what to do to retrieve a FileContent for a particular index:

  1. Reflect on the actual class of the data object to find a method called "GetData" that takes four arguments
  2. If the method was not found, reflect again to find a field of type System.Windows.IDataObject, get its value, and go back to step 1 (recursion is safe here)
  3. Use MethodInfo.Invoke to call the "GetData" you found with arguments: "FileContents", false, ComTypes.DVASPECT, lindex
  4. Read the file data from the returned MemoryStream

Here is the gist of the code to retrieve the file contents for a given index:

public MemoryStream GetFileContents(IDataObject dataObject, int index)
{
  MethodInfo getData = (
    from method in dataObject.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
    where method.Name=="GetData" && method.GetParameters().Length==4
    select method
  ).FirstOrDefault();

  if(getData==null)
  {
    FieldInfo innerField = (
      from field in dataObject.GetType().GetFields()
      where field.FieldType == typeof(IDataObject)
      select field
    ).FirstOrDefault();
    if(innerField==null) throw new Exception("Cannot get FileContents from DataObject of type" + dataObject.GetType());
    return GetFileContents((IDataObject)innerField.GetValue(dataObject), index);
  }

  return (MemoryStream)getData.Invoke(dataObject, new object[]
  {
    "FileContents", false,
    System.Runtime.InteropServices.ComTypes.DVASPECT.DVASPECT_CONTENT,
    index
  });
}
Ray Burns
Answers like that are the reason I love StackOverflow. Thanks, Ray!
Dylan Beattie