Zip files with the ZipFile class in C#

[example]

The static ZipFile class is relatively new (.NET Framework 4.5 and later), so this example uses Visual Studio 2019.

The following section explains how to load the ZipFile class in a Visual Studio 2019 project. The rest of this post explains the example program.

Using ZipFile

Create a new Windows Forms .NET Framework project. If you use .NET Core, some of this will be different. I’m not going to cover that, but you can probably figure it out.

The ZipFile class is stored in the System.IO.Compression namespace. Rather than just adding a reference to that as you would in older versions of Visual Studio, the currently approved way to add the necessary library to your project is to use the NuGet package manager. Fortunately adding NuGet packages is fairly easy.

In the Properties window, right-click Dependencies and select Manage NuGet Packages. Select the Browse tab and search for Sytem.IO.Compression.ZipFile. Click on that entry and click the Install button on the right. The Preview Changes dialog lists the package and any dependencies that it requires. Look over the list, wish you could do without all of those dependencies, and click OK. If you get a license dialog, click I Accept. After the installation finishes, close the NuGet Package Manager.

To make using the ZipFile class easier, add the directive using System.IO.Compression; to the top of the form’s code.

After you’ve added the package that contains ZipFile, you’re ready to build the user interface.

User Interface

Take a look at the picture at the top of the post. The large ListBox holds the files that you want to zip. Click the + button or use the File menu’s Add Files command to display a file open dialog that lets you select files to add to the list. If you select one or more files and click Open, the program adds the files to the list.

To remove one or more files from the list, select them and then click the X button or the File menu’s Remove Files command.

After you have selected the files that you want to zip, select the File menu’s Save Zip File command. In the file selection dialog that appears, enter or select the Zip file that you want to create and click Save.

Removing Files

The program contains some code to make the user interface work better. For example, it disables the X button and the File menu’s Remove Files command if there are not files selected in the list. Download the example to see that code.

I want to show one piece of user interface code because it highlights an important issue when removing items from a collection. When you click the X button or select the File menu’s Remove Files button, the following method executes.

private void RemoveFiles()
{
    // Remove the first selected item by index until none are selected.
    while (lstFiles.SelectedIndices.Count > 0)
    {
        lstFiles.Items.RemoveAt(lstFiles.SelectedIndices[0]);
    }

    // Remove the first selected item as an object until none are selected.
    //while (lstFiles.SelectedItems.Count > 0)
    //{
    //    lstFiles.Items.Remove(lstFiles.SelectedItems[0]);
    //}

    // Remove the (first) selected item until none are selected.
    //while (lstFiles.SelectedItem != null)
    //{
    //    lstFiles.Items.Remove(lstFiles.SelectedItem);
    //}

    // Remove the selected items by index in reverse order.
    //for (int i = lstFiles.SelectedIndices.Count - 1; i >= 0; i--)
    //{
    //    lstFiles.Items.RemoveAt(i);
    //}
    EnableMenuItems();
}

As long as the list has selected files, the program removes the first of them. When it removes the first selected file file, the indices of any files that come after it in the list decrease by 1. The SelectedIndices list updates so it holds the new indices of the remaining items, and the process can continue. The loop continues removing the first selected item from the list until there are no more selected items.

Another approach that you might try would be to make a variable, say i, loop through the indices of the selected items and remove them from the list. Unfortunately when you do that, the indices update and the variable i increments so it skips entries.

For example, the first time through the loop, the code removes the first selected index. Now the item that was second becomes the first (with index 0), but i increments to 1 so it is past the new first item.

You also cannot use a foreach loop to iterate through the SelectedItems or SelectedIndices list and remove the items in those lists. Whenever you remove an item, that modifies the lists and that messes up the foreach loop.

The code shows a few other strategies that work commented out. If you study them, you should be able to figure out how they work.

Zipping Files

The easiest way to zip files is to put them in the same directory and then use the ZipFile class’s CreateFromDirectory method to zip the directory. I didn’t take that approach because I wanted to show how to add specific files to a zip file.

When you add files to a zip archive, the file names within the archive are relative. That way when you unzip a directory hierarchy, the files go where they belong.

However, in this example you select individual files, so there isn’t a hierarchy. That’s fine unless you happen to select two files that have the same name because the zip archive cannot have two entries with the same name.

To solve that problem, the example appends a number to files with the same names. For example, if you select three files named myfile.txt, then inside the zip archive they are named myfile.txt, myfile(1).txt, and myfile(2).txt.

Without further ado, here’s the code. When you select the File menu’s Save Zip File command, its event handler calls the following SaveZipFile method.

private void SaveZipFile()
{
    // Get the Zip file name. If the user cancels, do nothing.
    if (sfdZipFile.ShowDialog() != DialogResult.OK) return;

    // If the file exists, delete it.
    // (The dialog made the user agree to that.)
    string zip_file_name = sfdZipFile.FileName;
    if (File.Exists(zip_file_name)) File.Delete(zip_file_name);

    Cursor = Cursors.WaitCursor;

    // Create the Zip archive for update.
    using (ZipArchive archive =
        ZipFile.Open(zip_file_name, ZipArchiveMode.Update))
    {
        // Add the files to the archive.
        for (int i = 0; i < lstFiles.Items.Count; i++)
        {
            // Select the file in the list so the user can see progress.
            lstFiles.SelectedIndex = i;
            lstFiles.Refresh();

            // Get the file's name without the path.
            string filename = lstFiles.Items[i].ToString();
            FileInfo file_info = new FileInfo(filename);

            // Make an entry name for the file.
            string entry_name = file_info.Name;
            for (int version = 1; version < 10000; version++)
            {
                ZipArchiveEntry old_entry = archive.GetEntry(entry_name);
                if (old_entry == null) break;
                entry_name =
                    Path.GetFileNameWithoutExtension(file_info.Name) +
                    $"({version})" + file_info.Extension;
            }

            // Add the file to this entry.
            archive.CreateEntryFromFile(filename, entry_name);
        }
    }

    lstFiles.SelectedIndex = -1;
    Cursor = Cursors.Default;
    MessageBox.Show("Done");
}

This code displays a SaveFileDialog to let you select the soon-to-be-created zip file. If you click Cancel, the method returns.

If you pick a file and click Save, the code gets the name of the file and uses File.Exists to see if the file already exists. If it does, the code uses File.Delete to delete it. (The SaveFileDialog asked if you wanted to replace the existing file.)

Next the code uses the ZipFile class’s Open method to create a new zip archive (which is stored in the file). The program uses the Update parameter so it can look at the file as it is constructed. Notice that the code places the archive in a using block so it is automatically disposed when the block finishes.

The program then loops through the files in the list box and gets each file’s name. It then enters a loop that runs until the code finds a name for this file that is not already in the archive.

Inside the loop, the program uses the archive’s GetEntry method to see if the file’s name is already in the archive. If the name is not already present, the code breaks out of the loop and uses the current name.

If the name is already in use, the code composes a new file name consisting of the original file’s name without the path or extension, followed by the loop number inside parentheses, followed by the original file’s extension. The loop continues until the program finds a file name that is not in use.

Notice how the code uses string interpolation $"({version})" to compose the file’s number. String interpolation is one of the best features added to C# in several versions. Unfortunately it’s a fairly recent development, so you can’t use it with older versions of C# (which load and run faster than the newer versions).

Having found a file name that is not already in use, the code uses the archive’s CreateEntryFromFile method to add the file to the archive using the recently composed name.

The program then continues to loop through the files in the list box, adding them to the archive.

Summary

This example uses the ZipFile class to add a collection of files to a zip archive. Because of the way it lets you select the files from arbitrary locations, it flattens the files’ directory hierarchy and places them all at the same level in the zip file.

Download the example to see additional details and to experiment with the program.


Download Example   Follow me on Twitter   RSS feed   Donate




About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in files and tagged , , , , , , , , , . Bookmark the permalink.