Compressing files with Delphi TZipFile native class
Overview
It’s very likely that every developer has ever used third-party components to work with file compression in Delphi. 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:
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.
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.