Inno Setup - 正确计时进度条的更新

Inno Setup - Correctly timing update of progress bar

我忘记了如何根据条件在 Inno Setup 中正确更新进度条,我写了一个代码来更新我在 Wizard 中创建的进度条。

问题是 ProgressBar 的最后一个位置分别为 95、96、97、98 或 100、101,并且当我运行安装程序时,它的更新有时不相同。我用来划分的值(这里是 6)不适用于所有系统,因为它们的性能与每个系统都非常不同。

我想知道一种正确更新进度条而不会出现此类问题的方法。

[Files]
Source: "C:\Program Files (x86)\Inno Setup 5\Examples\MyProg.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "C:\InnoCallback.dll"; DestDir: "{app}"; Flags: ignoreversion

[Code]
Type
  TTimerProc = procedure(HandleW, msg, idEvent, TimeSys: LongWord);

function SetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc: LongWord): LongWord; external 'SetTimer@User32.dll stdcall';
function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord; external 'wrapcallback@{tmp}\InnoCallback.dll stdcall delayload';
function KillTimer(hWnd, nIDEvent: LongWord): LongWord; external 'KillTimer@User32.dll stdcall';

var
  TTimer: LongWord;
  ConditionTracker: String;
  P: Integer;
  TestingPB: TNewProgressBar;

procedure Install;
var
  ErrorCode: Integer;
begin
  if ShellExec('Open', 'Timeout.exe', '/T 10', '', SW_HIDE, ewWaitUntilTerminated, ErrorCode) = True then
    ConditionTracker := 'DONE';
end;

procedure UpdateProgressBar(HandleW, msg, idEvent, TimeSys: LongWord);
begin
  if ConditionTracker = 'DONE' then begin
    KillTimer( 0, TTimer);
    TestingPB.State := npbsPaused;
  end else begin
    P := P + 1;
    Log('ProgressBar Position: ' + IntToStr(P div 6));
    TestingPB.Position := P div 6;
    if (P div 6) = 100 then P := 600;
  end;
end;

procedure InitializeWizard;
begin
  TestingPB := TNewProgressBar.Create(WizardForm);
  with TestingPB do begin
    Parent := WizardForm;
    Width := WizardForm.ProgressGauge.Width;
    Top := 200;
    Left := (WizardForm.ClientWidth - Width) div 2;
    Max := 100;
    Position := 0;
    Hide;
    ExtractTemporaryFile('InnoCallback.dll'); 
    P := 0;
  end;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
  if CurPageID = wpSelectTasks then begin
    TestingPB.Show;
    TTimer := SetTimer( 0, 0, 0, WrapTimerProc(@UpdateProgressBar, 4));
    Install;
  end else
    TestingPB.Hide;
end;

提前致谢。

两个主要问题是:

  • 你没有设置定时器间隔(SetTimer function的第三个参数)。然后该函数默认为 USER_TIMER_MINIMUM,也就是 10 毫秒。有些机器可能过于频繁,机器可能无法经常执行计时器。

    因此你在不同的机器上得到不同的结果。而且你代码中的魔法数字都是任意的。

    并且在所有机器上,您通过每秒执行 100 次计时器来严重浪费系统资源。

    使用一些合理且可达到的频率(至少 100 毫秒,甚至更多)。

  • 无论如何你不能依赖定时器调用的频率。系统不保证您调用定时器。特别是,如果系统忙于繁重的安装过程,计时器将不可靠。

    您应该根据实时时间进行计算。 GetTickCount function is commonly used for this. See How to get time difference in Inno Setup?


您的代码的其他问题:

  • 进度条应将页面 (WizardForm.SelectTasksPage) 作为其父页面。那么你就不需要显式隐藏和显示了。
  • 不要使用绝对坐标。至少缩放它们(ScaleXScaleY)。
    参见
  • 无需显式提取 InnoCallback.dll。请改用 external '...k@files:InnoCallback.dll 声明。不需要安装到{app}(除非你以后需要),使用Flags: dontcopy。虽然在 Inno Setup 6 中,有 CreateCallback function,所以你不需要你需要 InnoCallback.dll
  • ConditionTracker 应该是 Boolean,而不是 string

[Code]

function GetTickCount: DWord;
  external 'GetTickCount@kernel32 stdcall';
function SetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc: LongWord): LongWord;
  external 'SetTimer@User32.dll stdcall';
function KillTimer(hWnd, nIDEvent: LongWord): LongWord;
  external 'KillTimer@User32.dll stdcall';

var
  Timer: LongWord;
  Done: Boolean;
  TestingPB: TNewProgressBar;
  InitialTime: DWord;

const
  Duration = 10000;

procedure Install;
var
  ErrorCode: Integer;
begin
  if ShellExec('Open', 'Timeout.exe', '/T ' + IntToStr(Duration div 1000), '',
               SW_HIDE, ewWaitUntilTerminated, ErrorCode) then
  begin
    Done := True;
  end;
end;

procedure UpdateProgressBar(HandleW, msg, idEvent, TimeSys: LongWord);
begin
  if Done then
  begin
    KillTimer(0, Timer);
    TestingPB.State := npbsPaused;
    TestingPB.Position := TestingPB.Max;
  end
    else
  begin
    TestingPB.Position := GetTickCount - InitialTime;
  end;
end;

procedure InitializeWizard;
begin
  TestingPB := TNewProgressBar.Create(WizardForm);
  with TestingPB do
  begin
    Parent := WizardForm.SelectTasksPage;
    Width := WizardForm.ProgressGauge.Width;
    Left := WizardForm.ProgressGauge.Left;
    Top := ScaleY(200);
    Max := Duration;
    Position := 0;
  end;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
  if CurPageID = wpSelectTasks then
  begin
    Timer := SetTimer(0, 0, 100, CreateCallback(@UpdateProgressBar));
    InitialTime := GetTickCount;
    Install;
  end
end;