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?