Solved

VB.NET Background Worker can't update progress bar during file copy

Posted on 2007-12-03
3
8,139 Views
Last Modified: 2008-02-28
Please could someone help get this piece of code working? It is supposed to find all files of the type speciifed in My Documents and copy them to a temp folder. As the file copy is taking place, the progress bar and label should update stating each file name and the percentage of data copied.

It works fine without the handlers, but when I try to add the onerror and onchange handlers, the code will not compile and the error is that 'OnChange' is not an event of the form. Can someone please show me where I am going wrong?
Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Windows.Forms

Imports System.IO
 

Partial Public Class Form1

    Inherits Form
 

    ' Class to report progress 

    Public Class UIProgress

        Public name As String

        Public bytes As Long

        Public maxbytes As Long

        Public Sub New(ByVal name_ As String, ByVal bytes_ As Long, ByVal maxbytes_ As Long)

            name = name_

            bytes = bytes_

            maxbytes = maxbytes_

        End Sub

    End Class
 

    ' Class to report exception

    Public Class UIError

        Public Sub New(ByVal ex As Exception, ByVal path_ As String)

            msg = ex.Message

            path = path_

            result = DialogResult.Cancel

        End Sub

        Public msg As String

        Public path As String

        Public result As DialogResult

    End Class
 

    Private mCopier As 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
 

    Public Sub New()

        InitializeComponent()

        mCopier = New BackgroundWorker()

        AddHandler mCopier.DoWork, AddressOf Copier_DoWork

        AddHandler mCopier.RunWorkerCompleted, AddressOf Copier_RunWorkerCompleted

        mCopier.WorkerSupportsCancellation = True

        AddHandler OnChange, AddressOf Copier_ProgressChanged

        AddHandler OnError, AddressOf Copier_Error

        AddHandler Button1.Click, AddressOf Button1_Click

        ChangeUI(False)

    End Sub
 

    Private Sub Copier_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)

        ' Create list of files to copy 

        Dim theExtensions As String() = {"*.bmp", "*.png", "*.gif"}

        Dim files As New List(Of FileInfo)()

        Dim path As String = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)

        Dim dir As New DirectoryInfo(path)

        Dim maxbytes As Long = 0

        For Each ext As String In theExtensions

            Dim folder As FileInfo() = dir.GetFiles(ext, SearchOption.AllDirectories)

            For Each file As FileInfo In folder

                If (file.Attributes And FileAttributes.Directory) <> 0 Then

                    Continue For

                End If

                files.Add(file)

                maxbytes += file.Length

            Next

        Next

        ' Copy files 

        Dim bytes As Long = 0

        For Each file As FileInfo In files

            Try

                Me.BeginInvoke(OnChange, New Object() {New UIProgress(file.Name, bytes, maxbytes)})

                file.CopyTo("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.result = DialogResult.Cancel Then

                    Exit Try

                End If

            End Try

            bytes += file.Length

        Next

    End Sub
 

    Private Sub Copier_ProgressChanged(ByVal info As UIProgress)

        ' Update progress 

        ProgressBar1.Value = CInt((100 * info.bytes / info.maxbytes))

        Label1.Text = "Copying " + info.name

    End Sub
 

    Private Sub Copier_Error(ByVal err As UIError)

        ' Error handler 

        Dim msg As String = String.Format("Error copying file {0}" & Chr(10) & "{1}" & Chr(10) & "Click OK to continue copying files", err.path, err.msg)

        err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation)

    End Sub
 

    Private Sub Copier_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)

        ' Operation completed, update UI 

        ChangeUI(False)

    End Sub
 

    Private Sub ChangeUI(ByVal docopy As Boolean)

        Button1.Text = IIf(docopy, "Cancel", "Copy")

        Label1.Text = "Starting copy..."

        ProgressBar1.Value = 0

    End Sub
 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)

        Dim docopy As Boolean = Button1.Text = "Copy"

        ChangeUI(docopy)

        If docopy Then

            mCopier.RunWorkerAsync()

        Else

            mCopier.CancelAsync()

        End If

    End Sub

End Class

Open in new window

0
Comment
Question by:keyuk
  • 2
3 Comments
 
LVL 18

Assisted Solution

by:jcoehoorn
jcoehoorn earned 50 total points
ID: 20399554
Here's the correct signature for the changed event:
    Private Sub Copier_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs) Handles Copier.ProgressChanged

For the errors there is no direct event.  You handle the WorkCompleted event and check the value of the error property passed in the event args:
    Private Sub Copier_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles Copier.RunWorkerCompleted
        If e.Error IsNot Nothing Then
            'Any error handling code you need
            Throw e.Error        
        End If
    End Sub
         
0
 
LVL 1

Accepted Solution

by:
wizengamot earned 450 total points
ID: 20399813
There is another solution. The one I am pasting below should work just fine.  I have re-pasted the full code with my additions.  Please note that I am using Events here to process the required information back into various form level controls and this same method can be replicated for any object on the entire form whose properties you need to change in the background worker.

I have made as few changes as possible to your code and the changes I have made are commented accordingly.  This code has been tested by me (note that I changed the source folders to accomplish this as I am running vista) and the progress bar is updated fully as one would expect for a function of this type.  

I might make a suggestion for a different way of doing this however.  While the code below works just fine the manner in which I would change it would be to make two procedures.  I would implement a QUEUE object which would create a QUEUE of files and summarize their size to another variable.  I would then implement another worker process  (I like to use threads rather than worker processes) that processes the QUEUE.  This would tend to have the effect that as more files are processed by the worker thread the progress bar would have a tendency to recede as the maxbytes var gets larger.  If this is undesired behavior you could delay the start of the copy thread for a specified number of seconds or millseconds to give time to the first thread to generate a large enough queue so as this behavior would not be noticed.  This implementation would  be advantageous in two ways.
1.  Take advantage of the continuing rise of quad core machines.
2.  Only have to loop the files once, as the copy is just de-queuing the files.


The major drawback here is development time.  It would take a significant amount of extra time to do this in the manner specified and to debug it.  You would probably have to implement a MUTEX to prevent a runaway thread condition and to ensure that as the first thread adds to the QUEUE, the second thread cannot then dequeue a file.  It would be very complex debugging and woudl increase your dev time for this procedure by a number days at the very least.

Hope this helps.
Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Windows.Forms

Imports System.IO
 

Partial Public Class Form1

    Inherits Form
 

    'start of changes by wizengamot

    '*****************************************************************************

    Private Event UpdatePercent(ByVal Percent As Double)

    Delegate Sub SetValueUpdatePercentCompleteCallback(ByVal Percent As Double)
 

    Private Sub SetValueUpdatePercentUpdateComplete(ByVal Percent As Double)

        If ProgressBar1.InvokeRequired Then

            Dim d As New SetValueUpdatePercentCompleteCallback(AddressOf SetValueUpdatePercentUpdateComplete)

            Me.Invoke(d, New Object() {Percent})

        Else

            ProgressBar1.Value = Percent

        End If

    End Sub

    Sub EventHandler_UpdatePercentComplete(ByVal Percent As Double) Handles Me.UpdatePercent

        If Percent < 100 Then

            SetValueUpdatePercentUpdateComplete(Percent)

        Else

            SetValueUpdatePercentUpdateComplete(100)

        End If

    End Sub

    'End of changes by wizengamot

    '*****************************************************************************
 
 

    ' Class to report progress 

    Public Class UIProgress

        Public name As String

        Public bytes As Long

        Public maxbytes As Long

        Public Sub New(ByVal name_ As String, ByVal bytes_ As Long, ByVal maxbytes_ As Long)

            name = name_

            bytes = bytes_

            maxbytes = maxbytes_

        End Sub

    End Class
 

    ' Class to report exception

    Public Class UIError

        Public Sub New(ByVal ex As Exception, ByVal path_ As String)

            msg = ex.Message

            path = path_

            result = DialogResult.Cancel

        End Sub

        Public msg As String

        Public path As String

        Public result As DialogResult

    End Class
 

    Private mCopier As BackgroundWorker

    Private Delegate Sub ProgressChanged(ByVal info As UIProgress)

    Private Delegate Sub CopyError(ByVal err As UIError)
 
 
 
 

    Public Sub New()

        InitializeComponent()

        mCopier = New BackgroundWorker()

        AddHandler mCopier.DoWork, AddressOf Copier_DoWork

        AddHandler mCopier.RunWorkerCompleted, AddressOf Copier_RunWorkerCompleted

        mCopier.WorkerSupportsCancellation = True

        '*********************************************

        'these two lines removed by wizengamot.

        'AddHandler OnChange, AddressOf Copier_ProgressChanged

        'AddHandler OnError, AddressOf Copier_Error

        '***********************************************

        AddHandler Button1.Click, AddressOf Button1_Click

        ChangeUI(False)

    End Sub
 

    Private Sub Copier_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)

        ' Create list of files to copy 

        Dim theExtensions As String() = {"*.pdf", "*.png", "*.gif"}

        Dim files As New List(Of FileInfo)()

        Dim path As String = "c:\downloads"

        Dim dir As New DirectoryInfo(path)

        Dim maxbytes As Long = 0

        For Each ext As String In theExtensions

            Try

                Dim folder As FileInfo() = dir.GetFiles(ext, SearchOption.AllDirectories)

                For Each file As FileInfo In folder

                    If (file.Attributes And FileAttributes.Directory) <> 0 Then

                        Continue For

                    End If

                    files.Add(file)

                    maxbytes += file.Length

                Next

            Catch ex As Exception
 

            End Try
 

        Next

        ' Copy files 

        Dim bytes As Long = 0

        For Each file As FileInfo In files

            Try

                RaiseEvent UpdatePercent((bytes / maxbytes) * 100)

                '*****************************************************

                'Line removed by wizengamot

                'Me.BeginInvoke(OnChange, New Object() {New UIProgress(file.Name, bytes, maxbytes)})

                '*****************************************************

                file.CopyTo("c:\temp\" + file.Name, True)

            Catch ex As Exception

                Dim err As New UIError(ex, file.FullName)

                '*****************************************************

                'Line removed by wizengamot

                'Me.Invoke(OnError, New Object() {err})

                '*****************************************************

                If err.result = DialogResult.Cancel Then

                    Exit Try

                End If

            End Try

            bytes += file.Length

        Next

    End Sub
 

    Private Sub Copier_ProgressChanged(ByVal info As UIProgress)

        ' Update progress 

        ProgressBar1.Value = CInt((100 * info.bytes / info.maxbytes))

        Label1.Text = "Copying " + info.name

    End Sub
 

    Private Sub Copier_Error(ByVal err As UIError)

        ' Error handler 

        Dim msg As String = String.Format("Error copying file {0}" & Chr(10) & "{1}" & Chr(10) & "Click OK to continue copying files", err.path, err.msg)

        err.result = MessageBox.Show(msg, "Copy error", MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation)

    End Sub
 

    Private Sub Copier_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)

        ' Operation completed, update UI 

        ChangeUI(False)

    End Sub
 

    Private Sub ChangeUI(ByVal docopy As Boolean)

        Button1.Text = IIf(docopy, "Cancel", "Copy")

        Label1.Text = "Starting copy..."

        ProgressBar1.Value = 0

    End Sub
 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs)

        Dim docopy As Boolean = Button1.Text = "Copy"

        ChangeUI(docopy)

        If docopy Then

            mCopier.RunWorkerAsync()

        Else

            mCopier.CancelAsync()

        End If

    End Sub

End Class

Open in new window

0
 
LVL 1

Assisted Solution

by:wizengamot
wizengamot earned 450 total points
ID: 20399830
Please note, that I just commented out your errorhandler invoke as you can copy the implementation that I did for the the progress bar and implement the minor differences for your invoke of the errorhandler as well.  Again, I would just define an event for this purpose in the same way as I already demonstrated and call the event in your catch.
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

Displaying an arrayList in a listView using the default adapter is rarely the best solution. To get full control of your display data, and to be able to refresh it after editing, requires the use of a custom adapter.
Entering a date in Microsoft Access can be tricky. A typo can cause month and day to be shuffled, entering the day only causes an error, as does entering, say, day 31 in June. This article shows how an inputmask supported by code can help the user a…
Get people started with the process of using Access VBA to control Outlook using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Microsoft Outlook. Using automation, an Access applic…
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…

708 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now