Robert Wigley
Robert Wigley

Reputation: 1957

Inno Setup show directory copy progress bar and label on PrepareToInstall page

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

Answers (1)

Martin Prikryl
Martin Prikryl

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

Related Questions