Reputation: 1957
I am trying to show a progress bar and label on the PrepareToInstall page whilst copying (migrating) a previous installation to a new location. I am using a slightly modified version of Martin Prikryl's DirectoryCopy procedure and this works as expected; the files and directories are copied to the new location and the operations are logged to file.
However, whilst copying the files, which could be quite a long running operation if there are many (I tested this with 2,500 files, totalling about 1.2GB), the GUI does not update and appears to freeze, without displaying any of my custom controls (i.e. no progress bar and no progress label). I managed to force these to display by calling either Refresh
or Update
, but the progress bar is not animated and it appears the whole GUI is unresponsive whilst the copy operation completes. I think the fact that Inno Setup only supports single-threaded operations is maybe what is causing the GUI to freeze and not update. Is there a way to copy the files and have the GUI update at the same time?
[Code]
var
PrepareToInstallLabel: TNewStaticText;
PrepareToInstallProgressBar: TNewProgressBar;
//Slightly modified Public Domain code to copy a directory recursively and update PrepareToInstall label progress
//Contributed by Martin Prikryl on Stack Overflow
procedure DirCopy(strSourcePath, strDestPath: String);
var
FindRec: TFindRec;
strSourceFilePath, strDestFilePath: String;
begin
if FindFirst(strSourcePath + '\*', FindRec) then
begin
try
repeat
if (FindRec.Name <> '.') and (FindRec.Name <> '..') then
begin
strSourceFilePath := strSourcePath + '\' + FindRec.Name;
strDestFilePath := strDestPath + '\' + FindRec.Name;
if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
begin
PrepareToInstallLabel.Caption := 'Copying ' + strSourceFilePath + '...';
if FileCopy(strSourceFilePath, strDestFilePath, False) then
begin
Log(Format('Copied %s to %s', [strSourceFilePath, strDestFilePath]));
end
else
begin
SuppressibleMsgBox(Format('Failed to copy %s to %s', [strSourceFilePath, strDestFilePath]),
mbError, MB_OK, IDOK);
end;
end
else
begin
if CreateDir(strDestFilePath) then
begin
Log(Format('Created %s', [strDestFilePath]));
DirCopy(strSourceFilePath, strDestFilePath);
end
else
begin
SuppressibleMsgBox(Format('Failed to create %s', [strDestFilePath]),
mbError, MB_OK, IDOK);
end;
end;
end;
until
not FindNext(FindRec);
finally
FindClose(FindRec);
end;
end
else
begin
SuppressibleMsgBox(Format('Failed to list %s', [strSourcePath]),
mbError, MB_OK, IDOK);
end;
end;
//Show PrepareToInstall page GUI controls
procedure ShowPrepareToInstallGuiControls();
begin
PrepareToInstallProgressBar.Visible := True;
PrepareToInstallLabel.Visible := True;
end;
//Update PrepareToInstall page GUI controls; note this procedure should not be needed
procedure UpdatePrepareToInstallGuiControls();
begin
//Both lines below seem to be needed to force the Cancel button to disable,
//despite already disabling the button at the beginning of the PrepareToInstall event
WizardForm.CancelButton.Enabled := False;
WizardForm.CancelButton.Refresh;
//Both lines below seem to be needed to force display of the progress bar and label,
//despite already showing them in the PrepareToInstall event; without them no controls are shown on the page.
PrepareToInstallLabel.Update;
PrepareToInstallProgressBar.Update;
end;
//Hide PrepareToInstall page GUI controls
procedure HidePrepareToInstallGuiControls();
begin
PrepareToInstallProgressBar.Visible := False;
PrepareToInstallLabel.Visible := False;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
WizardForm.CancelButton.Enabled := False;
//Migrate installation
if IsMigration then
begin
ShowPrepareToInstallGuiControls;
PrepareToInstallLabel.Caption := 'Migrating installation...';
UpdatePrepareToInstallGuiControls;
Log('Installation migration started.');
ForceDirectories(ExpandConstant('{app}\FolderToMigrate'));
DirCopy(strExistingInstallPath + '\Database', ExpandConstant('{app}\FolderToMigrate'));
Log('Installation migration finished.');
end;
HidePrepareToInstallGuiControls;
end;
procedure InitializeWizard();
//Define the label for the Preparing to Install page
PrepareToInstallLabel := TNewStaticText.Create(WizardForm);
with PrepareToInstallLabel do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.StatusLabel.Left;
Top := WizardForm.StatusLabel.Top;
end;
//Define Progress Bar for the Preparing to Install Page
PrepareToInstallProgressBar := TNewProgressBar.Create(WizardForm);
with PrepareToInstallProgressBar do
begin
Visible := False;
Parent := WizardForm.PreparingPage;
Left := WizardForm.ProgressGauge.Left;
Top := WizardForm.ProgressGauge.Top;
Width := WizardForm.ProgressGauge.Width;
Height := WizardForm.ProgressGauge.Height;
Min := 0;
Max := 100;
Style := npbstMarquee;
end;
end;
Update: I added WizardForm.Refresh;
under PrepareToInstallLabel.Caption := 'Copying ' + strSourceFilePath + '...';
and this seems to force the label to update, but there is still no progress bar animation. Also, calling WizardForm.Refresh
thousands of times, after each file is copied, does not seem particularly efficient.
Upvotes: 1
Views: 858
Reputation: 202272
The easiest solution is to pump windows message queue in the repeat
...until
loop.
Or you can use TOutputProgressWizardPage
to present an operation progress.
I have added more details, including links to example implementations to
Inno Setup: How to modify long running script so it will not freeze GUI?
Upvotes: 1