Search code examples
c#wpfprogress-barunzip

Unzip File with active progress bar C#


I am trying to make a custom installer to extract a zip file from a fixed location into the users preferred location I have visited and found many sources, all of which do not work. The issue that the application freezes while unzipping the package and does not update the progress bar until its 100% completed ( not very useful in my opinion )

This is what I have so far

void Install()
{
      using (Ionic.Zip.ZipFile zip = Ionic.Zip.ZipFile.Read(Constants.UpdateZipPath))
      {
            zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(Zip_ExtractProgress);
            zip.ExtractAll(installDir, ExtractExistingFileAction.OverwriteSilently);
      }
}

void Zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
{
      if (e.TotalBytesToTransfer > 0)
      {
            ProgressBar.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
      }
}

This is one of the sources I found and does not work Extract ZipFile Using C# With Progress Report

When I try an use Task.Factory.StartNew(() => Install()); I get this Error

Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll
Exception thrown: 'System.InvalidOperationException' in DotNetZip.dll

I am using Ionic.Zip.ZipFile and when I use it outside of the main thread it doesnt work


Solution

  • I did get it to work with what Axel Kemper had, but I had to start a thread just before I started unzipping, here is the code I came up with!

    public class MyClass
    {
        public bool InstallIsCompleted = false;
        public string CurrentFileBeingExtracted = "";
        public int TotalNumberOfFiles = 1;
        public int NumberOfFilesTransferred = 1;
    
        public void MyFunction()
        {
            new Thread(Update).Start();
            Task.Factory.StartNew(() => Install());
        }
    
        void Update()
        {
            while (!InstallIsCompleted)
            {
                Dispatcher.Invoke(() =>
                {
                    // updates UI text elements including the display for which file
                    // is being extracted and the total progress of the total extraction
                    DisplayCurrentFile.Text = CurrentFileBeingExtracted;
                    float Percentage = (NumberOfFilesTransferred*100) / TotalNumberOfFiles;
                    InstallationProgressBar.Value = Percentage + (SecondProgressBar.Value * (1 / TotalNumberOfFiles));
                });
            }
            //Install Completed
            /* other code here */
        }
    
        void Install(string ZipPath, string TargetPath)
        {
            using (ZipFile zip = ZipFile.Read(ZipPath))
            {
                // initial setup before extraction
                TotalNumberOfFiles= zip.Entries.Count;
                zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(Zip_ExtractProgress);
                // actual extraction process
                zip.ExtractAll(TargetPath, ExtractExistingFileAction.OverwriteSilently);
                // since the boolean below is in the same "thread" the extraction must 
                // complete for the boolean to be set to true
                InstallIsCompleted = true;
            }
        }
    
        void Zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
        {
            // must be above 0 to prevent *divide by 0 error
            if (e.TotalBytesToTransfer > 0)
            {
                // If file has completed transfer, update the number of files transferred
                if (Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer) >= 100)
                {
                    NumberOfFilesTransferred++;
                }
                // updates the current file being exracted
                CurrentFileBeingExtracted = e.CurrentEntry.FileName.Replace("zip::", "");
                // updates the progress
                ProgressBarExtensions.SetProgressValue(InstallationProgressBar, Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer));
            }
        }
    }
    
    
    public static class ProgressBarExtensions
    {
            //  this extension method can be used for any Control which supports
            //  InvokeRequired() and BeginInvoke()
            public static void EnsureInvokeAsync(this Control control, Action action)
            {
                if (control.InvokeRequired)
                {
                    control.BeginInvoke(action);
                }
                else
                {
                    action();
                }
            }
    
            public static void SetProgressValue(this ProgressBar progressBar, int progressValue)
            {
                // https://stackoverflow.com/a/21969844/1911064
                // the lambda will capture the argument in a closure
                // the compiler does all the hard work for you
                progressBar.EnsureInvokeAsync(() => progressBar.Value = progressValue);
            }
        }
    
    

    This worked 100% like I wanted it to for me, although the progress calculation is not 100% accurate because it divides the total size by the number of files evenly, so a 10kb file out of a 1mb zip might be account for 25% completion if there are only 4 files.

    For some reason e.TotalBytesToTransfer only accounts for each file, and I'm not sure there is a solid way to calculate the extraction size before extraction, so in this instance its only possible to get the extraction progress per file, and calculate how many files there are