• Blog
  • Compressing files with Delphi TZipFile native class

Compressing files with Delphi TZipFile native class

Сode examples of compressing and extracting files

Publish date:
Discover more of what matters to you

Overview

It’s very likely that every developer has ever used third-party components to work with file compression in Delphi development. For instance, a few years ago, a component called TZipMaster became very popular and widely applied. Other developers preferred to work with the free 7-Zip utility app, which provides command-line functions to compress files.

Well, the good news is that they are not required anymore. Since XE2, Embarcadero has introduced a new Delphi native class to work with compression, called TZipFile, and has also been applying improvements to it over the years. One of them was released in the Seattle version. This new event allows developers to work with the compression status in real-time, providing information as:

  • Filename
  • File header with additional details
  • Bytes processed

So, by working with this event, it’s possible to display a percentage or a progress bar while the files are being compressed. Let’s bring all of this to a hands-on example.

Сode examples of compressing and extracting files

TZipFile is simple and straightforward to use. First of all, the namespace System.Zip should be declared in the unit “uses” section (“interface” preferably, in accordance with the further examples), and, secondly, just declare and create an object of TZipFile class to run the compression. Have a look at the example below:

1234567891011121314151617181920212223
uses
System.Zip;
var
ZipFile: TZipFile;
begin
// Creates a new instance of TZipFile
ZipFile := TZipFile.Create;
try
// Defines the filename which will be created
ZipFile.Open(GetCurrentDir + '\CompressedFiles.zip', zmWrite);
// Compress the files
ZipFile.Add('C:\Temp\Document.docx');
ZipFile.Add('C:\Temp\Worksheet.xlsx');
ZipFile.Add('C:\Temp\Presentation.pptx');
MessageDlg('Operation completed!', mtInformation, [mbOK], 0);
finally
// Frees the object from memory
ZipFile.Free;
end;
end;

Likewise, extracting a compressed file is very similar:

123456789101112131415
var
ZipFile: TZipFile;
begin
// Creates a new instance of TZipFile
ZipFile := TZipFile.Create;
try
// Extracts the compressed file
ZipFile.ExtractZipFile(GetCurrentDir + '\CompressedFiles.zip', GetCurrentDir);
MessageDlg('Operation completed!', mtInformation, [mbOK], 0);
finally
// Frees the object from memory
ZipFile.Free;
end;
end;

As you can see, there is really no secret at all.

However, that’s not everything that TZipFile provides. Let’s take a deeper look at the extended capabilities of the class.

Adding files dynamically

In the real world, filenames cannot be hard-coded. The application should allow the user to select the files that they want to compress. From the Dialogs palette, there is a component called TOpenDialog that can be applied for this purpose. Drop this component onto the form, and set the Options > ofAllowMultiSelect property to True, so that the user can select multiple files at once. As an improvement, a TListBox object can be used to display the selected files. The code snippet for the files selection would be similar to the one below:

123456789
var
SelectedFile: string;
begin
if OpenDialog1.Execute then
begin
for SelectedFile in OpenDialog1.Files do
ListBox1.Items.Add(SelectedFile);
end;
end;

Since the process is now dynamic, the method that compresses the files should be changed:

1234567891011121314151617181920
var
ZipFile: TZipFile;
FileItem: string;
begin
// Creates a new instance of TZipFile
ZipFile := TZipFile.Create;
try
// Defines the filename which will be created
ZipFile.Open(GetCurrentDir + '\CompressedFiles.zip', zmWrite);
// Reads the files added to the TListBox
for FileItem in ListBox1.Items do
ZipFile.Add(FileItem);
MessageDlg('Operation completed!', mtInformation, [mbOK], 0);
finally
// Frees the object from memory
ZipFile.Free;
end;
end;

Displaying the file compression progress

Do you remember the OnProgress event, mentioned at the beginning of this article? It can be used to get the number of bytes to be processed and bytes that have been already processed, so, by performing a calculation, the real-time compression progress can be displayed.

This event accepts a method with the following definition:

1
procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);

The first parameter represents the caller which, in this case, will always be an object of the TZipFile class type. The second parameter refers to the filename currently being compressed. The next parameter is an object of the TZipHeader class which carries additional information, such as header, size, CRC, compression method, and other details. Finally, the fourth parameter indicates the bytes that have already been processed during the compression process.Look at the example method below, considering that Label1 is the component that will display the percentage. The math is simple: divide the number of bytes processed by the file size and, then, multiply by 100 to find the proper percentage.

1234567891011121314151617181920
private
procedure OnProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
{ … }
procedure TForm1.OnProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
var
UncompressedSizePercentage: real;
begin
Application.ProcessMessages;
// Initializes the percentage variables as 1, case the selected file has zero size
UncompressedSizePercentage := 1;
if Header.UncompressedSize > 0 then
UncompressedSizePercentage := Position / Header.UncompressedSize;
// Displays the compression percentage
Label1.Caption := FormatFloat('#.## %', UncompressedSizePercentage * 100);
end;

Now, the final touch. You need to assign the method to the event:

123456
ZipFile := TZipFile.Create;
try
// Assigns the method to the event
ZipFile.OnProgress := OnProgressEvent;
{ … }

However, if the user selects two or more files, the calculation will restart for each file. From a UX point of view, it does not sound intuitive. Therefore, it would be better to display the overall compression progress.

Displaying the overall compression progress

The first step is to calculate the size of all files that have been selected. To do it, you can use the TFileStream class due to the Size property:

12345678910111213141516
private
function GetFileSize(const FileName: string): Integer;
{ … }
function TForm1.GetFileSize(const FileName: string): Integer;
var
StreamArchive: TFileStream;
begin
StreamArchive := TFileStream.Create(FileName, fmOpenRead);
try
Result := StreamArchive.Size;
finally
StreamArchive.Free;
end;
end;

The second step is to create two class variables: one for storing the size of all files, and another one for storing the number of bytes that have been already compressed. Although there is a parameter called Position in the OnProgress event, it’s important to remind that it’s restarted for each file, hence it is necessary to use variables. Also, these variables will be used in different methods, so they need to be at class scope.

123
private
BytesToProcess: Integer;
BytesProcessed: Cardinal;

The next step presupposes applying some changes to the main compression method to feed both variables:

123456789101112131415161718192021222324252627282930313233343536
var
ZipFile: TZipFile;
FileItem: string;
begin
BytesToProcess := 0;
BytesProcessed := 0;
// Sums the size of all selected files
for FileItem in ListBox1.Items do
BytesToProcess := BytesToProcess + GetFileSize(FileItem);
// Creates a new instance of TZipFile
ZipFile := TZipFile.Create;
try
// Assigns the method to the OnProgress event
ZipFile.OnProgress := OnProgressEvent;
// Defines the filename which will be created
ZipFile.Open(GetCurrentDir + '\CompressedFiles.zip', zmWrite);
// Reads the files added to the TListBox object to compress them
for FileItem in ListBox1.Items do
begin
ZipFile.Add(FileItem);
// Updates the variable that stores the number of bytes processed
BytesProcessed := BytesProcessed +
ZipFile.FileInfo[Pred(ZipFile.FileCount)].UncompressedSize;
end;
MessageDlg('Operation completed!', mtInformation, [mbOK], 0);
finally
// Frees the object from memory
ZipFile.Free;
end;
end;

Some changes are also required in the OnProgress event as it should display not only the compression progress of the current file but also the general compression (considering a new TLabel component called Label2):

1234567891011121314151617181920212223
procedure TForm1.OnProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
var
UncompressedSizePercentage: Real;
BytesProcessedPercentage: Real;
begin
Application.ProcessMessages;
// Initializes the percentage variables as 1, case the selected file has zero size
UncompressedSizePercentage := 1;
BytesProcessedPercentage := 1;
if Header.UncompressedSize > 0 then
UncompressedSizePercentage := Position / Header.UncompressedSize;
if BytesProcessed > 0 then
BytesProcessedPercentage := (BytesProcessed + Position) / BytesProcessed;
// Displays the current file compression progress
Label1.Caption := FileName + ': ' + FormatFloat('#.## %', UncompressedSizePercentage * 100);
// Displays the general compression progress (all files)
Label2.Caption := 'Overall Progress: ' + FormatFloat('#.## %', BytesProcessedPercentage * 100);
end;

Using progress bars to enhance the experience

Progress bars are widely used to give feedback to the user on a specific ongoing operation. Popular compressors, such as WinRAR and 7-Zip, use progress bars to display the compression and decompression progress. The same behavior can be created in Delphi applications.Drop two TProgressBar components (from the Win32 palette) onto the form. The only change will be required for the OnProgress event where you will need to update their positions. Besides, since the same calculations will be performed for the labels and progress bars, it will be sensible to store their results in local variables.

1234567891011121314151617181920212223242526272829
procedure TForm1.OnProgressEvent(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64);
var
CurrentFileProgress: Real;
OverallProgress: Real;
begin
Application.ProcessMessages;
CurrentFileProgress := 1;
OverallProgress := 1;
// Calculates the progress
if Header.UncompressedSize > 0 then
CurrentFileProgress := Position / Header.UncompressedSize;
if BytesToProcess > 0 then
OverallProgress := (BytesProcessed + Position) / BytesToProcess;
// Converts to percentages
CurrentFileProgress := CurrentFileProgress * 100;
OverallProgress := OverallProgress * 100;
// Updates the labels with each progress
Label1.Caption := FileName + ': ' + FormatFloat('#.## %', CurrentFileProgress);
Label2.Caption := 'Overall Progress: ' + FormatFloat('#.## %', OverallProgress);
// Updates the progress bars with each progress
ProgressBar1.Position := Trunc(CurrentFileProgress);
ProgressBar2.Position := Trunc(OverallProgress);
end;

If you need to use compression methods in your application, we recommend you consider TZipFile. At Embarcadero DocWiki, you can find detailed documentation that will help you to work with it.

Subscribe to our newsletter and get amazing content right in your inbox.

This field is required
This field is required Invalid email address

Thank you for subscribing!
See you soon... in your inbox!

confirm your subscription, make sure to check your promotions/spam folder

Subscribe to our newsletter and get amazing content right in your inbox.

You can unsubscribe from the newsletter at any time

This field is required
This field is required Invalid email address

You're almost there...

A confirmation was sent to your email

confirm your subscription, make sure to check
your promotions/spam folder