Solved

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

Posted on 2007-12-03
3
8,163 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

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
vb.net class 3 22
VBA: Insert New column with specific format type 12 39
Very Large data in MYSQL 7 73
Groupbox Control ? 2 19
A short article about a problem I had getting the GPS LocationListener working.
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
An introduction to basic programming syntax in Java by creating a simple program. Viewers can follow the tutorial as they create their first class in Java. Definitions and explanations about each element are given to help prepare viewers for future …
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …

828 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