Search code examples
c#windows-shellsharpshell

Select a file for renaming in SharpShell context menu


I'm using SharpShell to write a tiny new shell context menu item that copies the currently selected files to a new subfolder, then prompts the user for the directory's new name.

Searching StackOverflow, I found this answer. However, I'd like to do the same in SharpShell.

I will somehow have to fire SVSI_EDIT at it, which I can find buried deep in SharpShell.Interop, but I'm not sure how any of this works. I can't find any documentation or code samples whatsoever.

(Edit: I think finding out how to get a Pidl from a file name would be a good start, but maybe I don't really need that at all?)


Solution

  • You can start creating a project with SharpShell to register a new shell context menu like in this tutorial.

    Here, we have to define a class implementing SharpContextMenu. For simplicity we will create the menu for any filetype and always show it:

    [ComVisible(true)]
    [COMServerAssociation(AssociationType.AllFiles)]
    public class CopyFilesExtension : SharpContextMenu
    {
        protected override bool CanShowMenu()
        {
            return true;
        }
    
        protected override ContextMenuStrip CreateMenu()
        {
            var menu = new ContextMenuStrip();
    
            var copyFiles = new ToolStripMenuItem { Text = "Copy Files To Folder..." };
    
            copyFiles.Click += (sender, args) => CopyFiles();
            menu.Items.Add(copyFiles);
    
            return menu;
        }
    
        private void CopyFiles()
        {
            ...
        }
    } 
    

    But I'm sure you've done all this, the problem here is to implement the CopyFiles() method.

    One way to do this is showing a dialog asking for the name of the folder, something like this:

    CopyFilesDialog

    Then, implement CopyFiles() like so:

    private void CopyFiles()
    {
        using (var dialog = new CopyFileDialog())
        {
            if (dialog.ShowDialog() == DialogResult.OK)
            {
                var folder = Path.GetDirectoryName(SelectedItemPaths.First());
                var newFolder = Path.Combine(folder, dialog.FolderName);
    
                Directory.CreateDirectory(newFolder);
    
                foreach (var path in SelectedItemPaths)
                {
                    var newPath = Path.Combine(newFolder, Path.GetFileName(path));
                    File.Move(path, newPath);
                }
            }
        }
    }
    

    In above code, we asked for the name of the folder, then create the folder and finally move selected files to that folder.

    However, if you want to do it using Rename command in Windows Explorer we can start by importing some needed Win32 functions:

    class Win32
    {
        [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr ILCreateFromPath([In, MarshalAs(UnmanagedType.LPWStr)] string pszPath);
    
        [DllImport("shell32.dll")]
        public static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr[] apidl, int dwFlags);
    
        [DllImport("shell32.dll")]
        public static extern void ILFree(IntPtr pidl);
    }
    
    • ILCreateFromPath allows us get the PIDL from a filename.
    • SHOpenFolderAndSelectItems allow us select the file and send rename command.
    • ILFree frees unmanaged PIDL created.

    With these Win32 functions we can defines CopyFiles() as follows:

    private void CopyFiles()
    {
        var folder = Path.GetDirectoryName(SelectedItemPaths.First());
        var newFolder = Path.Combine(folder, "New Folder");
        Directory.CreateDirectory(newFolder);
    
        foreach (var path in SelectedItemPaths)
        {
            var newPath = Path.Combine(newFolder, Path.GetFileName(path));
            File.Move(path, newPath);
        }
    
        RenameInExplorer(newFolder);
    }
    
    
    private static void RenameInExplorer(string itemPath)
    {
        IntPtr folder = Win32.ILCreateFromPath(Path.GetDirectoryName(itemPath));
        IntPtr file = Win32.ILCreateFromPath(itemPath);
    
        try
        {
            Win32.SHOpenFolderAndSelectItems(folder, 1, new[] { file }, 1);
        }
        finally
        {
            Win32.ILFree(folder);
            Win32.ILFree(file);
        }
    }
    

    We can't use SharpShell.Interop.Shell32 since the only method available in this class is ShellExecuteEx() which is used to launch new processes.