Search code examples
c#.netvisual-studiowinformsdrag-and-drop

How do you programmatically get the file name when dragging and dropping from .Net Solution explorer?


I want to write an application that produces zip files by directly dragging and dropping files from Visual Studio Solution Explorer into my app.

I've used the following code snippet to catch the incoming DataObject:

private void lblIncludedFiles_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.FileDrop))
    {
        e.Effect = DragDropEffects.Copy;
    }
}

I've tried every possible value of DataFormats, all of them return false.


Solution

  • Since this task might be somewhat less simple than it looks like on paper, here's a sample procedure that should allow to get a list of the files dragged from the Visual Studio Solution Explorer panel.

    The DataFormats that Visual Studio generates are partially common (UnicodeText and Text), but the actual file list is passed on in a (classic) MemoryStream object that is not a common DataFormat: CF_VSSTGPROJECTITEMS.

    The MemoryStream contains Unicode text - the actual list of the Project+File Name tuples that are being dropped - separated by a pipe (|) symbol - and some other binary sections that I don't think is useful to describe here.

    The other non common/predefined format, VX Clipboard Descriptor Format, is also a MemoryStream object, but it's just an Unicode string that contains the Project name.


    In this sample, the combined elements that are part of the Drop are organized using a custom class object that contains informations about:

    • the name and UUID of the Project(s) where the files are coming from,
    • the Project path and file path (.[xxx]prj) ,
    • the name of the object that started the Drag&Drop operation,
    • a list of all files that are dropped, the Project they're part of and their raw type (.cs, .vb, .h, .png etc.)

    Pick a control that will receive the Drop and add the handlers:

    private void ctlVSDrop_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetFormats().Contains("CF_VSSTGPROJECTITEMS"))
        {
            e.Effect = DragDropEffects.Copy;
        }
    }
    
    private void ctlVSDrop_DragDrop(object sender, DragEventArgs e)
    {
        var vsObject = new VisualStudioDataObject(e.Data);
    }
    

    The class object, VisualStudioDataObject, contains the methods needed to extract the informations from the DataObject referenced by the DragDrop event DragEventArgs:

    (Tested with Visual Studio 2017)

    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;
    
    class VisualStudioDataObject
    {
        public VisualStudioDataObject(IDataObject data)
        {
            if (data is null) {
                throw new ArgumentNullException("IDataObject data", "Invalid DataObject");
            }
            FileList = new List<FileObject>();
            GetData(data);
        }
    
        public List<FileObject> FileList { get; private set; }
        public string ProjectUUID { get; private set; }
        public string ProjectPath { get; private set; }
        public string ProjectFilePath { get; private set; }
        public string StartingObject { get; private set; }
    
        public class FileObject
        {
            public FileObject(string project, string path, string type) {
                SourceProject = project;
                FilePath = path;
                FileType = type;
            }
            public string SourceProject { get; }
            public string FilePath { get; }
            public string FileType { get; }
        }
    
        private void GetData(IDataObject data)
        {
            List<string> formats = data.GetFormats(false).ToList();
            if (formats.Count == 0) return;
    
            foreach (string format in formats) {
                switch (format) {
                    case "UnicodeText":
                        StartingObject = data.GetData(DataFormats.UnicodeText, true).ToString();
                        break;
                    case "VX Clipboard Descriptor Format":
                        var projectMS = (MemoryStream)data.GetData(format, false);
                        projectMS.Position = 0;
                        string prjFile = Encoding.Unicode.GetString(projectMS.ToArray()).TrimEnd("\0".ToCharArray());
                        ProjectFilePath = prjFile;
                        ProjectPath = Path.GetDirectoryName(prjFile);
                        break;
                    case "CF_VSSTGPROJECTITEMS":
                        GetFileData((MemoryStream)data.GetData(format, false));
                        break;
                }
            }
        }
    
        private void GetFileData(MemoryStream ms)
        {
            string uuidPattern = @"\{(.*?)\}";
            string content = Encoding.Unicode.GetString(ms.ToArray());
            //Get the Project UUID and remove it from the data object
            var match = Regex.Match(content, uuidPattern, RegexOptions.Singleline);
            if (match.Success) {
                ProjectUUID = match.Value;
                content = content.Replace(ProjectUUID, "").Substring(match.Index);
    
                //Split the file list: Part1 => Project Name, Part2 => File name
                string[] projectFiles = content.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0; i < projectFiles.Length; i += 2) {
                    string sourceFile = projectFiles[i + 1].Substring(0, projectFiles[i + 1].IndexOf("\0"));
                    FileList.Add(new FileObject(projectFiles[i], sourceFile, Path.GetExtension(sourceFile)));
                }
            }
            else {
                FileList = null;
                throw new InvalidDataException("Invalid Data content");
            }
        }
    }