Search Wiki:
This is a look at the code included in the same WinForms VB project, here: https://archive.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=DotNetZip&ReleaseId=2016.

This is a basic Windows Forms application, written in VB, that creates a ZIP file. Though it is very simple, it forms the basis of extension. You can extend it to read and extract ZIP files, or to open existing zip files and modify them. You can extend it to use encryption, or ZIP64, or self-extracting archives, or streams. Lots of options with DotNetZip. But let's just stick to the basics for this illustration.

Here's the main logic in the app that creates the zip file:
    Private Sub DoSave(ByVal p As Object)
        Dim options As WorkerOptions = TryCast(p, WorkerOptions)
        Try
            Using zip1 As ZipFile = New ZipFile
                zip1.AddDirectory(options.Folder)
                Me._entriesToZip = zip1.EntryFileNames.Count
                Me.SetProgressBars()
                AddHandler zip1.SaveProgress, New EventHandler(Of SaveProgressEventArgs)(AddressOf Me.zip1_SaveProgress)
                zip1.Save(options.ZipName)
            End Using
        Catch exc1 As Exception
            MessageBox.Show(String.Format("Exception while zipping: {0}", exc1.Message))
            Me.btnCancel_Click(Nothing, Nothing)
        End Try
    End Sub

As you can see, there's a using clause, in which we create an instance of type ZipFile. Inside that clause we call the AddDirectory method on the ZipFile, and then add a progress event handler. Then we call Save, and the zip file is created. Very simple.

As I said, the application is very basic - in fact the most exotic thing in the application is a worker thread. Because it can take a while to ZIP up a large set of files, the application uses a worker thread, so that all the work does not occur on the UI thread and freeze the user interface. The worker thread approach is fairly common in Windows Forms apps. Nothing new here. This is a look at the code that runs in response to a button click, to kick off the worker thread:

    Private Sub KickoffZipup()
        Dim folderName As String = Me.TextBox1.Text
        If (((Not folderName Is Nothing) AndAlso (folderName <> "")) AndAlso ((Not Me.TextBox2.Text Is Nothing) AndAlso (Me.TextBox2.Text <> ""))) Then
            If File.Exists(Me.TextBox2.Text) Then
                If (MessageBox.Show(String.Format("The file you have specified ({0}) already exists.  Do you want to overwrite this file?", _
                                                  Me.TextBox2.Text), "Confirmation is Required", _
                                                  MessageBoxButtons.YesNo, MessageBoxIcon.Question) <> DialogResult.Yes) Then
                    Return
                End If
                File.Delete(Me.TextBox2.Text)
            End If
            Me._saveCanceled = False
            Me._nFilesCompleted = 0
            Me._totalBytesAfterCompress = 0
            Me._totalBytesBeforeCompress = 0
            Me.btnZipUp.Enabled = False
            Me.btnZipUp.Text = "Zipping..."
            Me.btnCancel.Enabled = True
            Me.lblStatus.Text = "Zipping..."
            Dim options As New WorkerOptions
            options.ZipName = Me.TextBox2.Text
            options.Folder = folderName
            Me._workerThread = New Thread(AddressOf DoSave)
            Me._workerThread.Name = "Zip Saver thread"
            Me._workerThread.Start(options)
            Me.Cursor = Cursors.WaitCursor
        End If
    End Sub

In that code, we do a few interesting things. First, we confirm that the user wants to overwrite an existing file.
Then, if we're still intending to create the file, we do some UI housekeeping - disabling buttons and resettnig labels, and setting some Form-wide counter variables.
Finally, we fill an object (of type WorkerOptions) with some settings, and then start the thread, passing that object to the DoSave method.

The only other point of interest in the app is the use of the progressbars. Because a zip operation can run for a long time, we'd like to be kind and show a progress bar. Actually there are two progressbars - one to gauge the progress through the archive, and another to show the progress for the current entry in the archive. Each archive might get thousands or tens of thousands of entries. And each entry might be several gigabytes. So a progressbar for each would be nice. We do this through a single progress method we register with the ZipFile, which you saw in the above snippet. Here's the code for the SaveProgress event:
    Private Sub zip1_SaveProgress(ByVal sender As Object, ByVal e As SaveProgressEventArgs)
        Select Case e.EventType
            Case ZipProgressEventType.Saving_AfterWriteEntry
                Me.StepArchiveProgress(e)
                Exit Select
            Case ZipProgressEventType.Saving_Completed
                Me.SaveCompleted()
                Exit Select
            Case ZipProgressEventType.Saving_EntryBytesRead
                Me.StepEntryProgress(e)
                Exit Select
        End Select
        If Me._saveCanceled Then
            e.Cancel = True
        End If
    End Sub

As you can see there are a few different kinds of progress events that the ZipFile instance can generate. AfterWriteEntry is called after each entry in the zip has been successfully written. If you zip up a folder with 101 files in it, you will get 101 of these events. With this event we advance a progress bar gauging the progress for the entire zip file. The Completed event fires just once, when the Save is done. The final flavor of event is EntryBytesRead - this fires multiple times for each entry being written to the archive. The event data includes total bytes transferred so far for the entry, and the total number of bytes TO transfer. With this we can easily run a progressbar.

Notice of course that we can cancel the save operation if appropriate. The _saveCanceled member variable is set in the UI thread if the user clicks the Cancel button (that logic is not shown here). In the progress event, we just check the _saveCanceled value, and if it is true, then we set e.Cancel to True, and return, and the Save operation will be cancelled.

This snippet shows the code for the per-archive progressbar:
    Private Sub StepArchiveProgress(ByVal e As SaveProgressEventArgs)
        If Me.progressBar1.InvokeRequired Then
            Me.progressBar1.Invoke(New SaveEntryProgress(AddressOf Me.StepArchiveProgress), New Object() {e})
        ElseIf Not Me._saveCanceled Then
            Me._nFilesCompleted += 1
            Me.progressBar1.PerformStep()
            Me._totalBytesAfterCompress = (Me._totalBytesAfterCompress + e.CurrentEntry.CompressedSize)
            Me._totalBytesBeforeCompress = (Me._totalBytesBeforeCompress + e.CurrentEntry.UncompressedSize)
            ' progressBar2 is the one dealing with the item being added to the archive
            ' if we got this event, then the add of that item (or file) is complete, so we 
            ' update the progressBar2 appropriately.
            Me.progressBar2.Value = Me.progressBar2.Maximum = 1
            MyBase.Update()
        End If
    End Sub

Fairly straightforward - I have to do the Invoke() thing because the progress event will come in on the worker thread, not on the UI thread. Each time through this method, I increment the count of files completed, and I step the progressbar. Very easy. I also keep an account of the total bytes before and after compressing. This is necessary only for later, if I want to give the user the vital stats of the Save operation.

Ok that was the per-archive progress bar. This snippet shows the code for the per-entry progress bar:
    Private Sub StepEntryProgress(ByVal e As SaveProgressEventArgs)
        If Me.progressBar2.InvokeRequired Then
            Me.progressBar2.Invoke(New SaveEntryProgress(AddressOf Me.StepEntryProgress), New Object() {e})
        ElseIf Not Me._saveCanceled Then
            If (Me.progressBar2.Maximum = 1) Then
                Dim entryMax As Long = e.TotalBytesToTransfer
                Dim absoluteMax As Long = &H7FFFFFFF
                Me._progress2MaxFactor = 0
                Do While (entryMax > absoluteMax)
                    entryMax = (entryMax / 2)
                    Me._progress2MaxFactor += 1
                Loop
                If (CInt(entryMax) < 0) Then
                    entryMax = (entryMax * -1)
                End If
                Me.progressBar2.Maximum = CInt(entryMax)
                Me.lblStatus.Text = String.Format("{0} of {1} files...({2})", (Me._nFilesCompleted + 1), Me._entriesToZip, e.CurrentEntry.FileName)
            End If
            Dim xferred As Integer = CInt((e.BytesTransferred >> Me._progress2MaxFactor))
            Me.progressBar2.Value = IIf((xferred >= Me.progressBar2.Maximum), Me.progressBar2.Maximum, xferred)
            MyBase.Update()
        End If
    End Sub

Again I have to do the Invoke() game to get on the right thread.

There's some goofy math involved there, with progress2MaxFactor. The key thing there: the TotalBytesToTransfer and BytesTransferred are Int64 quantities, because files can be larger than 4.2g. Conversely, the progressbar MaximumValue and Value properties are Int32. So I cannot simply use the TotalBytesToTransfer as the progressbar.Maximum, nor can I use BytesTransferred for progressbar.Value - there will be an overflow. So on the first event received for a particular entry, I divide the TotalBytesToTransfer by 2, repeatedly, until the result can fit into an Int32. This is the conversion factor. Then, with that event and with each successive event, I divide the BytesTransferred by the same conversion factor, and the result will most assuredly fit into an Int32. This result becomes the Value of the progressbar.

I haven't shown any logic for the cancel button that I mentioned previously. The OnClick event for the Cancel button is straightforward; it just sets the _saveCanceled Boolean. We saw previously that when this Boolean member variable is set, we cancel the save.

The full code for the form is here:
Imports System.IO
Imports Ionic.Zip
Imports System.Threading
 
Public Class Form1
    ' Delegates for invocation of UI from other threads
    Private Delegate Sub SaveEntryProgress(ByVal e As SaveProgressEventArgs)
    Private Delegate Sub ButtonClick(ByVal sender As Object, ByVal e As EventArgs)
 
    Private _workerThread As Thread
    Private _saveCanceled As Boolean
    Private _totalBytesAfterCompress As Long
    Private _totalBytesBeforeCompress As Long
    Private _nFilesCompleted As Integer
    Private _progress2MaxFactor As Integer
    Private _entriesToZip As Integer
 
    Private Sub btnDirBrowse_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnDirBrowse.Click
        Dim folderName As String = Me.TextBox1.Text
        Dim dlg1 As New FolderBrowserDialog
 
        dlg1.SelectedPath = IIf(Directory.Exists(folderName), folderName, "c:\")
        dlg1.ShowNewFolderButton = False
        If (dlg1.ShowDialog = DialogResult.OK) Then
            'Me._folderName = dlg1.get_SelectedPath
            Me.TextBox1.Text = folderName
        End If
    End Sub
 
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnZipUp.Click
        Me.KickoffZipup()
    End Sub
 
 
    Private Sub KickoffZipup()
        Dim folderName As String = Me.TextBox1.Text
        If (((Not folderName Is Nothing) AndAlso (folderName <> "")) AndAlso ((Not Me.TextBox2.Text Is Nothing) AndAlso (Me.TextBox2.Text <> ""))) Then
            If File.Exists(Me.TextBox2.Text) Then
                If (MessageBox.Show(String.Format("The file you have specified ({0}) already exists.  Do you want to overwrite this file?", _
                                                  Me.TextBox2.Text), "Confirmation is Required", _
                                                  MessageBoxButtons.YesNo, MessageBoxIcon.Question) <> DialogResult.Yes) Then
                    Return
                End If
                File.Delete(Me.TextBox2.Text)
            End If
            Me._saveCanceled = False
            Me._nFilesCompleted = 0
            Me._totalBytesAfterCompress = 0
            Me._totalBytesBeforeCompress = 0
            Me.btnZipUp.Enabled = False
            Me.btnZipUp.Text = "Zipping..."
            Me.btnCancel.Enabled = True
            Me.lblStatus.Text = "Zipping..."
            Dim options As New WorkerOptions
            options.ZipName = Me.TextBox2.Text
            options.Folder = folderName
            Me._workerThread = New Thread(AddressOf DoSave)
            Me._workerThread.Name = "Zip Saver thread"
            Me._workerThread.Start(options)
            Me.Cursor = Cursors.WaitCursor
        End If
    End Sub
 
    Private Sub DoSave(ByVal p As Object)
        Dim options As WorkerOptions = TryCast(p, WorkerOptions)
        Try
            Using zip1 As ZipFile = New ZipFile
                zip1.AddDirectory(options.Folder)
                Me._entriesToZip = zip1.EntryFileNames.Count
                Me.SetProgressBars()
                AddHandler zip1.SaveProgress, New EventHandler(Of SaveProgressEventArgs)(AddressOf Me.zip1_SaveProgress)
                zip1.Save(options.ZipName)
            End Using
        Catch exc1 As Exception
            MessageBox.Show(String.Format("Exception while zipping: {0}", exc1.Message))
            Me.btnCancel_Click(Nothing, Nothing)
        End Try
    End Sub
 
    Private Sub zip1_SaveProgress(ByVal sender As Object, ByVal e As SaveProgressEventArgs)
        Select Case e.EventType
            Case ZipProgressEventType.Saving_AfterWriteEntry
                Me.StepArchiveProgress(e)
                Exit Select
            Case ZipProgressEventType.Saving_Completed
                Me.SaveCompleted()
                Exit Select
            Case ZipProgressEventType.Saving_EntryBytesRead
                Me.StepEntryProgress(e)
                Exit Select
        End Select
        If Me._saveCanceled Then
            e.Cancel = True
        End If
    End Sub
 
 
 
    Private Sub StepArchiveProgress(ByVal e As SaveProgressEventArgs)
        If Me.progressBar1.InvokeRequired Then
            Me.progressBar1.Invoke(New SaveEntryProgress(AddressOf Me.StepArchiveProgress), New Object() {e})
        ElseIf Not Me._saveCanceled Then
            Me._nFilesCompleted += 1
            Me.progressBar1.PerformStep()
            Me._totalBytesAfterCompress = (Me._totalBytesAfterCompress + e.CurrentEntry.CompressedSize)
            Me._totalBytesBeforeCompress = (Me._totalBytesBeforeCompress + e.CurrentEntry.UncompressedSize)
            ' progressBar2 is the one dealing with the item being added to the archive
            ' if we got this event, then the add of that item (or file) is complete, so we 
            ' update the progressBar2 appropriately.
            Me.progressBar2.Value = Me.progressBar2.Maximum = 1
            MyBase.Update()
        End If
    End Sub
 
 
    Private Sub SaveCompleted()
        If Me.lblStatus.InvokeRequired Then
            Me.lblStatus.Invoke(New MethodInvoker(AddressOf SaveCompleted))
            'Me.lblStatus.Invoke(New MethodInvoker(Me, DirectCast(Me.SaveCompleted, IntPtr)))
        Else
            Me.lblStatus.Text = String.Format("Done, Compressed {0} files, {1:N0}% of original", Me._nFilesCompleted, ((100 * Me._totalBytesAfterCompress) / CDbl(Me._totalBytesBeforeCompress)))
            Me.ResetState()
        End If
    End Sub
 
 
    Private Sub StepEntryProgress(ByVal e As SaveProgressEventArgs)
        If Me.progressBar2.InvokeRequired Then
            Me.progressBar2.Invoke(New SaveEntryProgress(AddressOf Me.StepEntryProgress), New Object() {e})
        ElseIf Not Me._saveCanceled Then
            If (Me.progressBar2.Maximum = 1) Then
                Dim entryMax As Long = e.TotalBytesToTransfer
                Dim absoluteMax As Long = &H7FFFFFFF
                Me._progress2MaxFactor = 0
                Do While (entryMax > absoluteMax)
                    entryMax = (entryMax / 2)
                    Me._progress2MaxFactor += 1
                Loop
                If (CInt(entryMax) < 0) Then
                    entryMax = (entryMax * -1)
                End If
                Me.progressBar2.Maximum = CInt(entryMax)
                Me.lblStatus.Text = String.Format("{0} of {1} files...({2})", (Me._nFilesCompleted + 1), Me._entriesToZip, e.CurrentEntry.FileName)
            End If
            Dim xferred As Integer = CInt((e.BytesTransferred >> Me._progress2MaxFactor))
            Me.progressBar2.Value = IIf((xferred >= Me.progressBar2.Maximum), Me.progressBar2.Maximum, xferred)
            MyBase.Update()
        End If
    End Sub
 
 
 
 
 
    Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click
        If Me.lblStatus.InvokeRequired Then
            Me.lblStatus.Invoke(New ButtonClick(AddressOf Me.btnCancel_Click), New Object() {sender, e})
        Else
            Me._saveCanceled = True
            Me.lblStatus.Text = "Canceled..."
            Me.ResetState()
        End If
    End Sub
 
    Private Sub ResetState()
        Me.btnCancel.Enabled = False
        Me.btnZipUp.Enabled = True
        Me.btnZipUp.Text = "Zip it!"
        Me.progressBar1.Value = 0
        Me.progressBar2.Value = 0
        Me.Cursor = Cursors.Default
        If Not Me._workerThread.IsAlive Then
            Me._workerThread.Join()
        End If
    End Sub
 
    Private Sub SetProgressBars()
        If Me.ProgressBar1.InvokeRequired Then
            'Me.ProgressBar1.Invoke(New MethodInvoker(Me, DirectCast(Me.SetProgressBars, IntPtr)))
            Me.ProgressBar1.Invoke(New MethodInvoker(AddressOf SetProgressBars))
        Else
            Me.ProgressBar1.Value = 0
            Me.ProgressBar1.Maximum = Me._entriesToZip
            Me.ProgressBar1.Minimum = 0
            Me.ProgressBar1.Step = 1
            Me.ProgressBar2.Value = 0
            Me.ProgressBar2.Minimum = 0
            Me.ProgressBar2.Maximum = 1
            Me.ProgressBar2.Step = 2
        End If
    End Sub
 
End Class
 
Public Class WorkerOptions
    ' Fields
    Public Folder As String
    Public ZipName As String
 
    ' Other fields you may want to add later:
    'Public Comment As String
    'Public CompressionLevel As CompressionLevel
    'Public Encoding As String
    'Public Encryption As EncryptionAlgorithm
    'Public Password As String
    'Public Zip64 As Zip64Option
    'Public ZipFlavor As Integer
End Class
 

The full solution is available on the download page.
Last edited Feb 23 2009 at 2:08 AM  by Cheeso, version 5
Updating...
Page view tracker