You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Inno Setup定时器回调仅触发一次?如何实现进度条周期性动画

问题:TOutputMarqueeProgressWizardPage跑马灯进度条仅触发一次动画

我尝试在执行长时间操作时,在TOutputMarqueeProgressWizardPage上显示跑马灯进度条,使用SetTimer函数调用UpdateProgress回调函数,脚本如下:

[Setup]
AppId={{F25E1D25-DF59-4B37-AD32-1EBD5F6A3CA6}
AppName="My Program"
AppVersion=1.5
CreateAppDir=no
OutputBaseFilename=setup

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Files]
Source: "WhatsAppSetup.exe"; Flags: ignoreversion dontcopy

[Code]
var
  CustomPage: TOutputMarqueeProgressWizardPage;
  CustomPageAfterID: Integer;

procedure InitializeWizard;
begin
  CustomPage := CreateOutputMarqueeProgressPage('CreateOutputMarqueeProgressPage', 'ADescription');
  CustomPageAfterID := wpReady;  
end;

function SetTimer(Wnd: LongWord; IDEvent, Elapse: LongWord; TimerFunc: LongWord): LongWord;
external 'SetTimer@user32.dll stdcall';
function KillTimer(hWnd: LongWord; uIDEvent: LongWord): BOOL;
external 'KillTimer@user32.dll stdcall';

procedure UpdateProgress();
begin
  Log('Animating');
  CustomPage.Animate;
end;

function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
Timer: LongWord;
begin
  Log('Page ID: ' + IntToStr(CurPageId));
  if CurPageID = CustomPageAfterID then begin
    try
      Timer := SetTimer(0, 0, 250, CreateCallback(@UpdateProgress));
      CustomPage.Show;
      ExtractTemporaryFile('WhatsAppSetup.exe');
      Exec(ExpandConstant('{tmp}\WhatsAppSetup.exe'), '--silent', '', SW_SHOW, ewWaitUntilTerminated, ResultCode);
      Sleep(20000);
    finally
      KillTimer(0, Timer);
      CustomPage.Hide;
    end;
  end;
  Result := True;
end;

但回调仅被调用一次,日志输出片段:

[20:59:38,970]   Extracting temporary file: C:\Users\user\AppData\Local\Temp\is-DCI5S.tmp\WhatsAppSetup.exe
[20:59:39,783]   Animating
[20:59:59,931]   Found 0 files to register with RestartManager.

需要修复该问题,实现进度条的周期性动画。


解决方案

问题根源

Windows定时器的回调依赖主线程的消息循环处理,但Inno Setup的主线程在执行Exec(..., ewWaitUntilTerminated)和Sleep这类阻塞操作时,会完全卡住消息队列,导致定时器消息无法被持续触发——这就是回调仅执行一次的原因。

修复方法

放弃依赖SetTimer,改用主动处理消息循环+定期调用进度条动画的方式,确保长时间操作期间UI能响应并更新。

修改后的NextButtonClick函数示例:

function NextButtonClick(CurPageID: Integer): Boolean;
var
  ResultCode: Integer;
  ProcessHandle: THandle;
begin
  Log('Page ID: ' + IntToStr(CurPageId));
  if CurPageID = CustomPageAfterID then begin
    CustomPage.Show;
    try
      ExtractTemporaryFile('WhatsAppSetup.exe');
      // 启动外部程序但不等待,改为手动等待进程结束,期间处理消息
      if Exec(ExpandConstant('{tmp}\WhatsAppSetup.exe'), '--silent', '', SW_SHOW, ewNoWait, ResultCode) then
      begin
        ProcessHandle := ResultCode;
        // 循环等待进程结束,同时更新进度条并处理消息
        while WaitForSingleObject(ProcessHandle, 100) = WAIT_TIMEOUT do
        begin
          CustomPage.Animate;
          ProcessMessages; // 关键:让主线程处理消息队列
        end;
        CloseHandle(ProcessHandle);
      end;

      // 模拟长时间操作的部分也改用循环处理
      for var I := 0 to 199 do
      begin
        CustomPage.Animate;
        ProcessMessages;
        Sleep(100);
      end;
    finally
      CustomPage.Hide;
    end;
  end;
  Result := True;
end;

核心改动说明

  1. 将Exec的等待模式从ewWaitUntilTerminated改为ewNoWait,手动通过WaitForSingleObject循环等待进程结束,每次等待超时后更新进度条并调用ProcessMessages处理消息。
  2. 把原来的Sleep(20000)拆分成多次小睡眠+进度条更新+消息处理,避免主线程长时间阻塞。
  3. 去掉了SetTimer和KillTimer的依赖,直接在循环中主动调用CustomPage.Animate,配合ProcessMessages保证UI响应。

内容的提问来源于stack exchange,提问作者qloq

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:07:46