copy file + progress bar

rlangi05

Member
Joined
Jan 2, 2006
Messages
21
Programming Experience
1-3
Hi guys... I want to copy a file from one folder to another but I want a progress bar to show the progress of the transfer. Im using VB .NET 2003. Can anyone help me with a link or sample code.

Thanks.
 
To do it you have to read the byte count as the copy is in progress. No basic IO file copy method provide this info, but with one IO.FileStream for reading and one for writing you read-write byte-by-byte and can update a ProgressBar during the copy process.
This example should help, there is a ProgressBar with maxvalue 100 on form for progress percent:
VB.NET:
Sub copyfilewithprogress()
  Dim path As String = Application.StartupPath & "\"
  Dim mediafile As String = "Amy Diamond - What's in it for me.mp3"
  Dim fi As New IO.FileInfo(mediafile)
  Dim sr As New IO.FileStream(path & mediafile, IO.FileMode.Open) 'source file
  Dim sw As New IO.FileStream(path & "copy of " & mediafile, IO.FileMode.Create) 'target file, defaults overwrite
  Dim len As Long = sr.Length - 1
  For i As Long = 0 To len
    sw.WriteByte(sr.ReadByte)
    If i Mod 1000 = 0 Then 'only update UI every 1 Kb copied
      ProgressBar1.Value = i * 100 / len
      Application.DoEvents()
    End If
  Next
  ProgressBar1.Value = 0
End Sub
 
Thank you for that! I want to note, however, that if you add a function for cancelling the operation, you have to call also, in the cancellation function:
VB.NET:
sr.close
sw.Close
If you don't do this, then your files will be locked by your application and you won't be able to restart the copy process.

@JohnH - Aside from the progress bar, how does this function differ from file.copy? I know file.copy will return an error if the files are missing.
 
@JohnH - Aside from the progress bar, how does this function differ from file.copy? I know file.copy will return an error if the files are missing.
FileStream with FileMode.Open requires an existing file, as documented FileNotFoundException is thrown when not.

Also see that this thread is very old and for .Net 1.1. For .Net 2.0 and onwards you can use My.Computer.FileSystem object, its CopyFile method has options for progress UI and also cancellation.
 
Actually, the filestream works perfectly (.net 4). I attached it to a background worker. It updates my progressbar and allows me to cancel the copy progress. I filestream the original file to a temp file in the new directory, then, if the progress = 100, and the filesize of the old file matches the filesize of the new file, then I give that temp file the correct name. If the progress was cancelled or there was an error, then I delete the tempfile.
FileStream with FileMode.Open requires an existing file, as documented FileNotFoundException is thrown when not.
Yes, I realized that, so I wrote my own Exists() function to account for that.

I am thinking, however, of using checksums to verify that the new file matches the old file, however. I saw in debug.print that the filesize can differ by one or two bytes, so there wasn't much use in comparing file sizes based on the length of the streamed file.
 
Actually, the filestream works perfectly (.net 4).
Of course is does, all other file IO tools use it also, including FileSystem.CopyFile. What I'm saying is that many additions has been made to .Net framework since the first version, to make common programming tasks much simpler for the developer.
however. I saw in debug.print that the filesize can differ by one or two bytes
A plain byte copy of a file can't differ by even a single byte, if it does something is wrong.
 
A plain byte copy of a file can't differ by even single byte, if it does something is wrong.
That's what I thought too. I think I had the debug.print in the wrong spot... It is copying perfectly now.

That snippet you placed there has helped me out alot! I had tried so many other options. Thank you very much. I shall say a prayer for you.
 
Mr. John H - How do I modify this function to allow for smaller files? I have a file that I use for testing that is ony 14kb, and I can't copy it. I think it has to do with the mod function. How can I keep the ui updating every 1kb, but still qualify the code to copy the file too? I admit, I don't completely understand what is going on.

Now, I have the io function wrapped into a worker.DoWork function... so you may notice the FileCopyWorker.ReportProgress(progress) - that is so I can run the io process on a worker thread.

VB.NET:
   Private Sub FileCopyWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles FileCopyWorker.DoWork
       Try Dim temppath As String = GetDirFromPath(whatdestinationfile) & "temp"
            Dim fi As New IO.FileInfo(temppath)
            Dim sr As New IO.FileStream(whatoriginationfile, IO.FileMode.Open) 'source file
            Dim sw As New IO.FileStream(temppath, IO.FileMode.Create) 'target file, defaults overwrite
            Dim len As Long = sr.Length - 1
            MessengerLogger.Info("Please wait whilst I upload " & whatoriginationfile)
            Dim i As long
            For i = 0 To len
                If FileCopyWorker.CancellationPending Then
                    MessengerLogger.Error("The transfer was cancelled at: " & progress & "%.")
                    Exit For
                End If
                sw.WriteByte(sr.ReadByte)
                If i Mod 1000 = 0 Then 'only update UI every 1 Kb copied
                    progress = i * 100 / len
                    FileCopyWorker.ReportProgress(progress)
                    Debug.Print(progress)
                    Application.DoEvents()
                End If
            Next
            sr.close
            sw.close
            If progress = 100 then
                'Debug.Print("finished:" & progress)
                File.Move(temppath, whatdestinationfile)
            Else
                'Debug.Print("cancelled:" & progress)
                File.Delete(temppath)
            end if
        Catch IOerror As Exception
            MessengerLogger.Error("There was an error in the procedure: " & IOerror.Message & " :CopyFileWithProgress().")
        End Try
    End Sub
 
I see no reason the code would't work on any size file. It copies one-by-one byte from source to target file, while reporting every 1kb progress - that's it and that's all.

Maybe your problem is that you look for "progress=100", instead use a Boolean to indicate when operation has completed.

I would also like to add, the code example was just that, a basic example to show how to read bytes from a source and write them to a target, while calculating progress. Normally for file copy/move operations you would read/write larger chunks of bytes each loop iteration, resulting in wastly faster IO operations.
 
Thanks for helping out. I figured the copy progress was a little slow. I don't understand why progress=100 wouldn't work. I test for that, because if progress is < 100, then either there was an error or the user cancelled the progress, which allows me to do some cleanup or notifications to the user.

I understand it was a basic example, which is what I appreciated. I think the reason it doesn't copy smaller files (and it doesn't) is because it only does the work if imod 1000 = 0. If i doesn't evaluate correctly in the mod, then it doesn't work right.

As you can see from my tests, here below, it has nothing to do with progress = 100. It has to do with the mod function.

Now, I understand that the mod function is supposed to provide an interval for the progress to update, but i mod 1000 either isn't in the right place, or isn't the right expression, I think.


I plugged this code into FastSharp (try it if you haven't - it lets you quickly evaluate expressions without using the VS IDE, and its free, too!)
VB.NET:
dim i as long
dim progress as long
dim m as long
dim t as long = 14
for i = 0 to t
  m = i mod 1000
' console.write(vbcr & "mod=" & m)
if m = 0 then
  progress = i * 100 / t
    console.write(vbcr & "i =" & i & vbcr)
  console.write("progress=" & progress & "%")
end if
next

Look at the output for t=14:
VB.NET:
i =0
progress=0%

Now, we make t = 1400
VB.NET:
i =0
progress=0%
i =1000
progress=71%

Finally, with t = 14000
VB.NET:
i =0
progress=0%
i =1000
progress=7%
i =2000
progress=14%
i =3000
progress=21%
i =4000
progress=29%
i =5000
progress=36%
i =6000
progress=43%
i =7000
progress=50%
i =8000
progress=57%
i =9000
progress=64%
i =10000
progress=71%
i =11000
progress=79%
i =12000
progress=86%
i =13000
progress=93%
i =14000
progress=100%


However, if t=1000
VB.NET:
i =0
progress=0%
i =1000
progress=100%

but t=100
VB.NET:
i =0
progress=0%
 
Hey, I think I might have gotten it - and you may have been right. I moved things around a bit...
VB.NET:
   [COLOR=#0000ff][B]Private[/B][/COLOR] [COLOR=#0000ff][B]Sub[/B][/COLOR] FileCopyWorker_DoWork([COLOR=#0000ff][B]ByVal[/B][/COLOR] sender [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#0c8910][B]Object[/B][/COLOR], [COLOR=#0000ff][B]ByVal[/B][/COLOR] e [COLOR=#0000ff][B]As[/B][/COLOR] System.ComponentModel.DoWorkEventArgs) [COLOR=#0000ff][B]Handles[/B][/COLOR] FileCopyWorker.DoWork
       whattempdestinationfile = GetDirFromPath(whatdestinationfile) & [COLOR=#f99304]"temp"[/COLOR]
        [COLOR=#0000ff][B]Try[/B][/COLOR]
            [COLOR=#0000ff][B]Dim[/B][/COLOR] fi [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#fc0a02]New[/COLOR] IO.FileInfo(whattempdestinationfile)
            [COLOR=#0000ff][B]Dim[/B][/COLOR] sr [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#fc0a02]New[/COLOR] IO.FileStream(whatoriginationfile, IO.FileMode.Open) [COLOR=#a9aaa9]'source file[/COLOR]
            [COLOR=#0000ff][B]Dim[/B][/COLOR] sw [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#fc0a02]New[/COLOR] IO.FileStream(whattempdestinationfile, IO.FileMode.Create) [COLOR=#a9aaa9]'target file, defaults overwrite[/COLOR]
            [COLOR=#0000ff][B]Dim[/B][/COLOR] len [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#0c8910][B]Long[/B][/COLOR] = sr.Length - 1
            MessengerLogger.Info([COLOR=#f99304]"Please wait whilst I upload "[/COLOR] & whatoriginationfile)
            [COLOR=#0000ff][B]Dim[/B][/COLOR] i [COLOR=#0000ff][B]As[/B][/COLOR] [COLOR=#0c8910][B]long[/B][/COLOR]
            [COLOR=#0000ff][B]For[/B][/COLOR] i = 0 [COLOR=#0000ff][B]To[/B][/COLOR] len
                [COLOR=#0000ff][B]If[/B][/COLOR] FileCopyWorker.CancellationPending [COLOR=#0000ff][B]Then[/B][/COLOR]
                    MessengerLogger.[COLOR=#0000ff][B]Error[/B][/COLOR]([COLOR=#f99304]"The transfer was cancelled at: "[/COLOR] & progress & [COLOR=#f99304]"%."[/COLOR])
                    [COLOR=#0000ff][B]Exit[/B][/COLOR] [COLOR=#0000ff][B]For[/B][/COLOR]
                [COLOR=#0000ff][B]End[/B][/COLOR] [COLOR=#0000ff][B]If[/B][/COLOR]
                sw.WriteByte(sr.ReadByte)
                progress = i * 100 / len
                [COLOR=#0000ff][B]If[/B][/COLOR] i [COLOR=#fc0a02]Mod[/COLOR] 1000 = 0 [COLOR=#fc0a02]Or[/COLOR] progress = 100 [COLOR=#0000ff][B]Then[/B][/COLOR] [COLOR=#a9aaa9]'only update UI every 1 Kb copied[/COLOR]
                    FileCopyWorker.ReportProgress(progress)
                    Application.DoEvents()
                [COLOR=#0000ff][B]End[/B][/COLOR] [COLOR=#0000ff][B]If[/B][/COLOR]
                
            [COLOR=#0000ff][B]Next[/B][/COLOR]
            sr.close
            sw.close
            
        [COLOR=#0000ff][B]Catch[/B][/COLOR] IOerror [COLOR=#0000ff][B]As[/B][/COLOR] Exception
            MessengerLogger.[COLOR=#0000ff][B]Error[/B][/COLOR]([COLOR=#f99304]"There was an error in the procedure: "[/COLOR] & IOerror.Message & [COLOR=#f99304]" :CopyFileWithProgress()."[/COLOR])
        [COLOR=#0000ff][B]End[/B][/COLOR] [COLOR=#0000ff][B]Try[/B][/COLOR]
    [COLOR=#0000ff][B]End[/B][/COLOR] [COLOR=#0000ff][B]Sub[/B][/COLOR]
[\code]
 
I don't understand why progress=100 wouldn't work
If copy for example is stopped after 995 out of 1000 bytes then progress would be at 100, but copy operation was not completed.
There are only two possible outcomes if a line is executed after the copy loop, either the operation was cancelled (CancellationPending) or it was completed.
I think the reason it doesn't copy smaller files (and it doesn't)
How could it not? The progress reporting is unrelated to the copy operation.
sr.close
sw.Close
If an exception is thrown those are not called. Either utilize the Using statement, or put them in Finally block of the Try statement.
I moved things around a bit
Now you are calculating percentage for each byte, which is unnecessary. While not a big waste of CPU cycles, it still is, always something to think about.
FastSharp - it lets you quickly evaluate expressions without using the VS IDE
It if was better than VS IDE I would use it ;) Faster? I doubt it produces code faster than VS intellisense.
 
How could it not? The progress reporting is unrelated to the copy operation.
Okay, I see that now. The IO functions run regardless unless the user cancels the process, in which case it gets interrupted, or there is an error, but my progressbar is only affected by the i mod part. It is important, therefore, to make sure sw.writebyte(sr.readbyte) is NOT inside the mod block.

I don't see what to do to make it faster or more efficient. To be guaranteed of 100 for progress, despite the mod function, I could do this:
VB.NET:
If i mod 1000 = 0 or i = len then...

If an exception is thrown those are not called. Either utilize the Using statement, or put them in Finally block of the Try statement.
Good point about the exception. I should have realized that.
If I use Finally, then I can't dim the filestream declarations locally. I don't understand how to use Using, unless it is a resource.

I added, per your advice a boolean that is defined as false if the user cancels the operation, or if there was an IO error, but true if i = len.
VB.NET:
            For i = 0 To len
                If FileCopyWorker.CancellationPending Then
                    FileStatus = false
                    MessengerLogger.Warn("You cancelled the transfer at: " & progress & "%.")
                    Exit For
                End If
                sw.WriteByte(sr.ReadByte)
                progress = i * 100 / len
               
                If i Mod 1000 = 0 or i = len Then 'only update UI every 1 Kb copied
                        If i = len then FileStatus = true
                    FileCopyWorker.ReportProgress(progress)
                    Application.DoEvents()
                End If
            Next

Now you are calculating percentage for each byte, which is unnecessary.
Okay, I agree, but what do I do? How can I make sure the progressbar follows the IO stream more efficiently. Maybe mod 10000 instead of mod 1000?

Yeah, I suppose VS is as good, but Fastsharp gives me a separate window, and I can leave my immediates window docked. I can't tell that it is slower or faster, actually.
 
If i mod 1000 = 0 or i = len then...
Why? 100% progress is something you can report after the copy loop if not cancelled. You can even do that from RunWorkerCompleted event handler.
How can I make sure the progressbar follows the IO stream more efficiently. Maybe mod 10000 instead of mod 1000?
I guess that depends on the file size copied. Look at Windows copy, you rarely see progress indication for a single file copy under 1mb, the operation is simply too fast and completes almost before it is possible to display a progress GUI. So time is a factor here, maybe you could disregard the particular byte progress and just look at the time since last progress update, for example only report progress once per second?
I don't understand how to use Using, unless it is a resource.
Any disposable class instance is a resource. The code sample is using a TextWriter instance for example. Though Using statement is not such a good idea here, the Using block swallows all exceptions, and here you need to know if an exception is thrown.
 
Mr. JohnH, I thank you again for your time!

I declared the filestreams outside of the sub and defined them new within the try loop, which allowed me to use Finally to close the filestreams.

I think my poor brain is finally getting it.

VB.NET:
     If FileCopyWorker.CancellationPending Then
                    FileStatus = false
                    MessengerLogger.Warn("You cancelled the transfer at: " & progress & "%.")
                    Exit For
                End If
                sw.WriteByte(sr.ReadByte)
                If i Mod 1000 = 0 Then 'only update UI every 1 Kb copied
                    progress = i * 100 / len
                    FileCopyWorker.ReportProgress(progress)
                    Application.DoEvents()
                End If
                If i = len then FileStatus = true
            Next

Then, in the RunWorkerCompleted function, I compare size of the old and new files and made sure the progressbar received a value of 100 upon success. The catch statement sets FileStatus to false if there was an exception.

Private Sub FileCopyWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles
VB.NET:
FileCopyWorker.RunWorkerCompleted
        If filestatus then
            File.Move(whattempdestinationfile, whatdestinationfile)
            If GetFileSize(whatdestinationfile) = GetFileSize(whatoriginationfile) then 
                MessengerLogger.Info(msg_finished)
                MakeThreadedNotifications() 'that is, send an email to notify someone
            End If 
            whatprogressbar.Value = 100
        Else
            MessengerLogger.Warn(msg_toobad)
            File.Delete(whattempdestinationfile)
        End If
        whatstartbutton.Enabled = True
        whatcancelbutton.Enabled = False
    End Sub

maybe you could disregard the particular byte progress and just look at the time since last progress update, for example only report progress once per second?
Do you think it is the monitoring of the byte progress that makes the action slower? I understand about the progress being too fast to monitor for small files.
 
Back
Top