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;
核心改动说明
- 将
Exec的等待模式从ewWaitUntilTerminated改为ewNoWait,手动通过WaitForSingleObject循环等待进程结束,每次等待超时后更新进度条并调用ProcessMessages处理消息。 - 把原来的
Sleep(20000)拆分成多次小睡眠+进度条更新+消息处理,避免主线程长时间阻塞。 - 去掉了
SetTimer和KillTimer的依赖,直接在循环中主动调用CustomPage.Animate,配合ProcessMessages保证UI响应。
内容的提问来源于stack exchange,提问作者qloq
相关产品推荐
相关产品推荐

