Introduction
Showing the progress of a copy operation is quite a common question on programming forums such as Codeguru.com. With today’s article, I will shed some light on this topic for you.
Before I continue, I must explain a few of the technologies that we’re going to use during the course of this article.
Delegates
A delegate is simply a type that represents a reference to physical methods with the same parameter list and return type. Okay, what does that mean in simple terms? A delegate is basically a substitute for a method with the same return type and with the same signature. We make use of a delegate when we cannot access certain threads directly (as explained in this article), or when we need to ensure that managed and unmanaged code can be executed properly.
Delegates (C# Programming Guide) at MSDN has a more detailed explanation of delegates.AddHandler/AddressOf.
Here is more information on the AddressOf operator.
BackgroundWorker
Here is more information on the BackgroundWorker class.
Now that you have a basic understanding of all the terms and objects you will encounter during this project, we can continue to a practical example.
Practical
Create a new Visual Basic Windows Forms project. The design should resemble Figure 1.
Figure 1: Our design
Code
Add the required namespaces:
Imports System.Collections.Generic Imports System.ComponentModel Imports System.IO
Add the following variable objects:
Private bwCopier As New BackgroundWorker Private Delegate Sub ProgressChanged(ByVal info As UIProgress) Private Delegate Sub CopyError(ByVal err As UIError) Private OnChange As ProgressChanged Private OnError As CopyError
With the preceding code, I created a BackgroundWorker object that will be responsible for running the copy operation in the background. That is why you need the two delegates: ProgressChanged and CopyError. Lastly, I created two objects that will consume both delegates. You may have noticed that, when creating the two delegates, they refer to separate classes. Let us create them now.
UIProgress Class
Public Class UIProgress Public strName As String Public lngBytes As Long Public lngMaxBytes As Long Public Sub New(ByVal FileName As String, _ ByVal Bytes As Long, ByVal MaxBytes As Long) strName = FileName Bytes = Bytes MaxBytes = MaxBytes End Sub End Class
There isn’t much in this class. This class is simply responsible for updating the form’s interface as the copy progresses.
UIError Class
Public Class UIError Public strErrorMsg As String Public strFilePath As String Public drResult As DialogResult Public Sub New(ByVal ex As Exception, _ ByVal FilePath As String) strErrorMsg = ex.Message strFilePath = FilePath drResult = DialogResult.Cancel End Sub End Class
This simple class just throws an error when something goes wrong in the copy operation. Let’s go on to the rest of the Form’s code. Add the constructor:
Public Sub New() InitializeComponent() AddHandler bwCopier.DoWork, AddressOf DoCopy AddHandler bwCopier.RunWorkerCompleted, _ AddressOf WorkerCompleted bwCopier.WorkerSupportsCancellation = True OnChange = AddressOf ChangeProgress OnError = AddressOf ErrorThrow UpdateUI(False) End Sub
Here, I connected the backgroundworker’s event to our own methods. We will add these methods as we progress through this article.
Add the DoCopy procedure:
Private Sub DoCopy(ByVal sender As Object, _ ByVal e As DoWorkEventArgs) Dim arrExtensions As String() = _ {"*.jpg", "*.jpeg", "*.bmp", "*.png", "*.gif"} Dim lstFiles As New List(Of FileInfo) Dim strFolderPath As String = _ Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) Dim diDirectory As New DirectoryInfo(strFolderPath) Dim lngMaxBytes As Long = 0 For Each strExt As String In arrExtensions Dim fiFolder As FileInfo() = diDirectory.GetFiles(strExt, _ SearchOption.AllDirectories) For Each fiFile As FileInfo In fiFolder If ((fiFile.Attributes And FileAttributes.Directory) <> 0) _ Then Continue For lstFiles.Add(fiFile) lngMaxBytes += fiFile.Length Next Next Dim lngBytes As Long = 0 For Each file As FileInfo In lstFiles Try Me.BeginInvoke(OnChange, New Object() _ {New UIProgress(file.Name, lngBytes, lngMaxBytes)}) System.IO.File.Copy(file.FullName, "c:\temp\" + _ file.Name, True) Catch ex As Exception Dim err As New UIError(ex, file.FullName) Me.Invoke(OnError, New Object() {err}) If err.drResult = Windows.Forms.DialogResult.Cancel _ Then Exit For End Try lngBytes += file.Length Next End Sub
Add the ChangeProgress procedure:
Private Sub ChangeProgress(ByVal info As UIProgress) ProgressBar1.Value = CInt(100.0 * _ info.lngBytes / info.lngMaxBytes) Label1.Text = "Copying " + info.strName End Sub
Add the next procedures:
Private Sub ErrorThrow(ByVal err As UIError) Dim msg As String = String.Format("Error _ copying file {0}\n{1}\nClick OK to continue _ copying files", Err.strFilePath, Err.strErrorMsg) err.drResult = MessageBox.Show(msg, "Copy error", _ MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation) End Sub Private Sub WorkerCompleted(ByVal sender As Object, _ ByVal e As RunWorkerCompletedEventArgs) UpdateUI(False) End Sub Private Sub UpdateUI(ByVal docopy As Boolean) Label1.Visible = docopy ProgressBar1.Visible = docopy If docopy Then Button1.Text = "Cancel" _ Else Button1.Text = "Copy" Label1.Text = "Starting copy..." ProgressBar1.Value = 0 End Sub
Finally, add the Button’s click event:
Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click Dim docopy As Boolean = Button1.Text = "Copy" UpdateUI(docopy) If (docopy) Then bwCopier.RunWorkerAsync() _ Else bwCopier.CancelAsync() End Sub
Conclusion
The next time you have to use copying logic within your applications, I hope you find this article useful. Until next time, cheers!