Showing the Progress of a Copy Process with Backgroundworker and Progressbar

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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.

Copy
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!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read