0

I have a WPF application that displays files in a folder. The user can select multiple files and select to delete them, currently I am using this logic with uses FileSystem from the VisualBasic.FileIO library:

foreach (Item item in items)
{
     if (item.IsDirectory)
     {
          FileSystem.DeleteDirectory(item.FullPath, UIOption.AllDialogs, RecycleOption.SendToRecycleBin);
     }
     else
     {
          FileSystem.DeleteFile(item.FullPath, UIOption.AllDialogs, RecycleOption.SendToRecycleBin);
     }
} 

The problem here is that if users have the Windows option "Display delete confirmation dialog" turned on: enter image description here

They get a Windows prompt for each file.

I want them to get a single prompt like this:

enter image description here

Is there a way to do this?

Even if it involves a PInvoke of some WinAPI function?

Ross Patterson
  • 257
  • 1
  • 3
  • 10
  • [`File.Delete`](https://learn.microsoft.com/en-us/dotnet/api/system.io.file.delete?view=net-6.0) – MindSwipe Apr 14 '22 at 08:27
  • 1
    @MindSwipe How does that help? File.Delete still takes a single path as a parameter? Also File.Delete doesn't show the windows prompt as far as I am aware. I still want the windows prompt, just not one for every file. – Ross Patterson Apr 14 '22 at 08:33
  • As far as I'm aware, you can't delete multiple files at once with a single method, or at least I couldn't find any. My suggestion is you build your own "Are you sure?" dialog and then just do it without showing the built in Windows prompt if the user accepted – MindSwipe Apr 14 '22 at 08:49
  • @MindSwipe That's what I initially suggested too, but then I realized the OP wants the warning dialog to only be shown if the user has it enabled in their system settings (which can even be different per drive). – 41686d6564 stands w. Palestine Apr 14 '22 at 09:31
  • @41686d6564 I'd wager you could query that on a pre drive level as well, so knowing the path of the file, you could query the system and only ask if the system has that flag set. Otherwise I'd add a setting to the application itself, enabling/ disabling that for any and all file deletion operation, ignoring if the system has the flag set or not. But seeing your answer, I'd use that as well – MindSwipe Apr 14 '22 at 10:15

1 Answers1

3

With PInvoke, we can use SHFileOperation with the FO_DELETE function to send file system objects to the recycle bin. According to the documentation, we can send multiple paths at once by joining them with a NULL character:

Although this member is declared as a single null-terminated string, it is actually a buffer that can hold multiple null-delimited file names. Each file name is terminated by a single NULL character. The last file name is terminated with a double NULL character ("\0\0") to indicate the end of the buffer.

Instead of writing everything from scratch, let's use parts of the code in this answer and adjust it to work with multiple paths. We will have something like this:

public class FileOperationAPIWrapper
{
    /// <summary>
    /// Possible flags for the SHFileOperation method.
    /// </summary>
    [Flags]
    public enum FileOperationFlags : ushort
    {
        /// <summary>
        /// Do not show a dialog during the process
        /// </summary>
        FOF_SILENT = 0x0004,
        /// <summary>
        /// Do not ask the user to confirm selection
        /// </summary>
        FOF_NOCONFIRMATION = 0x0010,
        /// <summary>
        /// Delete the file to the recycle bin.  (Required flag to send a file to the bin
        /// </summary>
        FOF_ALLOWUNDO = 0x0040,
        /// <summary>
        /// Do not show the names of the files or folders that are being recycled.
        /// </summary>
        FOF_SIMPLEPROGRESS = 0x0100,
        /// <summary>
        /// Surpress errors, if any occur during the process.
        /// </summary>
        FOF_NOERRORUI = 0x0400,
        /// <summary>
        /// Warn if files are too big to fit in the recycle bin and will need
        /// to be deleted completely.
        /// </summary>
        FOF_WANTNUKEWARNING = 0x4000,
    }

    /// <summary>
    /// File Operation Function Type for SHFileOperation
    /// </summary>
    public enum FileOperationType : uint
    {
        /// <summary>
        /// Move the objects
        /// </summary>
        FO_MOVE = 0x0001,
        /// <summary>
        /// Copy the objects
        /// </summary>
        FO_COPY = 0x0002,
        /// <summary>
        /// Delete (or recycle) the objects
        /// </summary>
        FO_DELETE = 0x0003,
        /// <summary>
        /// Rename the object(s)
        /// </summary>
        FO_RENAME = 0x0004,
    }

    /// <summary>
    /// SHFILEOPSTRUCT for SHFileOperation from COM
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct SHFILEOPSTRUCT
    {
        public IntPtr hwnd;
        [MarshalAs(UnmanagedType.U4)]
        public FileOperationType wFunc;
        public string pFrom;
        public string pTo;
        public FileOperationFlags fFlags;
        [MarshalAs(UnmanagedType.Bool)]
        public bool fAnyOperationsAborted;
        public IntPtr hNameMappings;
        public string lpszProgressTitle;
    }

    [DllImport("shell32.dll", CharSet = CharSet.Auto)]
    private static extern int SHFileOperation(ref SHFILEOPSTRUCT FileOp);

    public static bool SendToRecycleBin(string path, FileOperationFlags flags)
    {
        return SendToRecycleBin(new[] { path }, flags);
    }

    public static bool SendToRecycleBin(IList<string> paths, FileOperationFlags flags)
    {
        try
        {
            var fs = new SHFILEOPSTRUCT
            {
                wFunc = FileOperationType.FO_DELETE,
                pFrom = string.Join("\0", paths) + '\0' + '\0',
                fFlags = FileOperationFlags.FOF_ALLOWUNDO | flags
            };
            SHFileOperation(ref fs);
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }
}

Usage:

FileOperationAPIWrapper.SendToRecycleBin(items,
    FileOperationAPIWrapper.FileOperationFlags.FOF_WANTNUKEWARNING);