(Print this page)

Using the Windows dialog to copy/move/delete files and folders from a .Net application
Published date: Friday, July 26, 2019
On: Moer and Éric Moreau's web site

A friend asked me if I had a snippet of code to reproduce the dialog that Windows is showing when you copy or move or delete files and folders. I didn’t. But a quick put-the-name-of-your-favorite-search-engine-here session provided almost everything I needed.

Available source code

The downloadable demo solution has been created using Visual Studio 2019. Both VB and C# code are available. Even if the solution has been created using VS2019, the code can surely be used in previous versions.

Building the UI

As you can see in figure 1, the demo application has a very simple UI. A few labels, textboxes, and buttons and we are all set.

Figure 1: The demo app in action

The browse buttons

I thought that having buttons to let the user browse to find folders was a nice addition. The 2 buttons on the right containing ellipses are offering this feature.

The code is very easy as it relies on the FolderBrowserDialog class from the framework.

Both buttons call the same BrowseForFolder method which return the selected path. I have added a Boolean parameter to control the visibility of the “New folder button” which does not make to be visible when you select your source folder.

Private Sub BtnBrowseSource_Click(sender As Object, e As EventArgs) Handles btnBrowseSource.Click
    Dim strFolder As String = BrowseForFolder(False)
    If Not String.IsNullOrWhiteSpace(strFolder) Then txtSource.Text = strFolder
End Sub

Private Sub BtnBrowseDestination_Click(sender As Object, e As EventArgs) Handles btnBrowseDestination.Click
    Dim strFolder As String = BrowseForFolder(True)
    If Not String.IsNullOrWhiteSpace(strFolder) Then txtDestination.Text = strFolder
End Sub

Private Function BrowseForFolder(ByVal pShowNewFolderButton As Boolean) As String
    Dim strFolder As String = String.Empty
    Using objBrowser As New FolderBrowserDialog
        objBrowser.Description = "Select a folder"
        objBrowser.ShowNewFolderButton = pShowNewFolderButton
        If objBrowser.ShowDialog = DialogResult.OK Then
            strFolder = objBrowser.SelectedPath
        End If
    End Using
    Return strFolder
End Function
private void BtnBrowseSource_Click(object sender, EventArgs e)
{
    string strFolder = BrowseForFolder(false);
    if (!string.IsNullOrWhiteSpace(strFolder))
        txtSource.Text = strFolder;
}

private void BtnBrowseDestination_Click(object sender, EventArgs e)
{
    string strFolder = BrowseForFolder(true);
    if (!string.IsNullOrWhiteSpace(strFolder))
        txtDestination.Text = strFolder;
}

private string BrowseForFolder(bool pShowNewFolderButton)
{
    string strFolder = string.Empty;
    using (FolderBrowserDialog objBrowser = new FolderBrowserDialog())
    {
        objBrowser.Description = "Select a folder";
        objBrowser.ShowNewFolderButton = pShowNewFolderButton;
        if (objBrowser.ShowDialog() == DialogResult.OK)
            strFolder = objBrowser.SelectedPath;
    }
    return strFolder;
}

The Copy files button

This one has a bit more code. And most of the code relies on calls to the Win 32 API (where all the Windows dialogs can be found). A good part of this code is almost a copy-and-paste from PInvoke.Net (http://www.pinvoke.net/default.aspx/shell32.SHFileOperation) where you find almost everything for your Win32 calls.

Private Enum FileOperationsFunctions As UInteger
    Copy = &H2
    Delete = &H3
    Move = &H1
    Rename = &H4
End Enum

<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
Private Structure SHFILEOPSTRUCT
    Public hwnd As IntPtr
    Public wFunc As FileOperationsFunctions
    <MarshalAs(UnmanagedType.LPWStr)> Public pFrom As String
    <MarshalAs(UnmanagedType.LPWStr)> Public pTo As String
    Public fFlags As UShort
    Public fAnyOperationsAborted As Boolean
    Public hNameMappings As IntPtr
    <MarshalAs(UnmanagedType.LPWStr)> Public lpszProgressTitle As String
End Structure

<DllImport("shell32.dll", CharSet:=CharSet.Unicode)>
Private Shared Function SHFileOperation(<[In]> ByRef lpFileOp As SHFILEOPSTRUCT) As Integer
End Function
private enum FileOperationsFunctions : uint
{
    Copy = 0x0002,
    Delete = 0x0003,
    Move = 0x0001,
    Rename = 0x0004,
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 
private struct SHFILEOPSTRUCT
{
    public IntPtr hwnd;
    public FileOperationsFunctions wFunc;
    [MarshalAs(UnmanagedType.LPWStr)] public string pFrom;
    [MarshalAs(UnmanagedType.LPWStr)] public string pTo;
    public ushort fFlags;
    public bool fAnyOperationsAborted;
    public IntPtr hNameMappings;
    [MarshalAs(UnmanagedType.LPWStr)] public string lpszProgressTitle;
}

[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
static extern int SHFileOperation([In] ref SHFILEOPSTRUCT lpFileOp);

private static SHFILEOPSTRUCT _ShFile;

After you have these declarations, you can now add some code to call these features.

Private Sub BtnCopyFiles_Click(sender As Object, e As EventArgs) Handles btnCopyFiles.Click
    btnCopyFiles.Enabled = False

    If String.IsNullOrWhiteSpace(txtSource.Text) Then
        MessageBox.Show("You need to specify a Source folder")
        Return
    End If

    If String.IsNullOrWhiteSpace(txtDestination.Text) Then
        MessageBox.Show("You need to specify a Destination folder")
        Return
    End If

    CopyFiles(txtSource.Text, txtDestination.Text)

    btnCopyFiles.Enabled = True
End Sub

Private Sub CopyFiles(ByVal sSource As String, ByVal sTarget As String)
    Try
        _ShFile.wFunc = FileOperationsFunctions.Copy
        _ShFile.pFrom = sSource & vbNullChar & vbNullChar
        _ShFile.pTo = sTarget & vbNullChar & vbNullChar

        SHFileOperation(_ShFile)
    Catch ex As Exception
        MessageBox.Show(ex.Message)
    End Try
End Sub
private void BtnCopyFiles_Click(object sender, EventArgs e)
{
    btnCopyFiles.Enabled = false;

    if (string.IsNullOrWhiteSpace(txtSource.Text))
    {
        MessageBox.Show("You need to specify a Source folder");
        return;
    }

    if (string.IsNullOrWhiteSpace(txtDestination.Text))
    {
        MessageBox.Show("You need to specify a Destination folder");
        return;
    }

    CopyFiles(txtSource.Text, txtDestination.Text);

    btnCopyFiles.Enabled = true;
}

private void CopyFiles(string sSource, string sTarget)
{
    try
    {
        _ShFile.wFunc = FileOperationsFunctions.Copy;
        _ShFile.pFrom = sSource + "\0\0";
        _ShFile.pTo = sTarget + "\0\0";

        SHFileOperation(ref _ShFile);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Now if you run the code, you will see a very familiar dialog shown in figure 2. Note that this dialog varies according to the version you are running.

Figure 2: Figure 2: Looks familiar?

Was not working on my first trials!

When I first started to test the demo, it wasn’t working when I first click the button. Sometimes, it took 2 or even 3 clicks, some other times, it just didn’t run at all. It was working a bit better when running for the x86 processor but still not 100% reliable.

When you play with API calls, declarations are very sensible. You cannot just switch Integers for Longs or vice-versa. Therefore, PInvoke.Net is very valuable.

But my main issue was fixed simply by adding 2 null characters to the source and destination folders.

This is done like this in VB.Net:

     _ShFile.pFrom = sSource & vbNullChar & vbNullChar
     _ShFile.pTo = sTarget & vbNullChar & vbNullChar

Or if you prefer C#:

     _ShFile.pFrom = sSource + "\0\0";
     _ShFile.pTo = sTarget + "\0\0";

Other operations available

As you can see in the FileOperationsFunctions enumeration, operations such as Delete, Move and Rename are also available. I leave it up to you to find out how they work!

Conclusion

Instead of rebuilding my own dialog and try to support all the features that a user would expect, why not reuse the one available from Windows to which users are already used to?


(Print this page)