Inno Setup 在 WizardForm 最小化和恢复时检测(得到通知)

Inno Setup Detect (get notified) when WizardForm minimizes and restores

我正在我的 Pascal 脚本中添加一个 BASS 音频项目。为很长的安装添加音乐播放对用户来说并不坏。但是当用户将 WizardForm 最小化到任务栏时最好停止音乐。如果用户再次从任务栏恢复音乐,则自动启动音乐。

我想知道如何检测WizardForm是否被最小化或恢复,并根据WizardForm的window状态暂停或开始播放BASS音乐。 (使用 BASS_PauseBASS_StopBASS_Start. 等函数)

我应该如何以及应该选择什么来做到这一点? TWindowStateWMSYSCOMMAND?

提前致谢。

我不认为有任何事件会在向导表单最小化或恢复时通知您。 TForm.WindowState 在 Inno Setup Pascal 脚本中也不可用。


但您可以安排一个频繁的计时器并检查表单状态的变化。

请注意,当您单击最小化按钮(没有最小化动画)时,表单实际上是隐藏的,而不是最小化的。所以使用 GetWindowLong WinAPI function 检查 WS_VISIBLE window 样式。

[Code]

const
  GWL_STYLE = -16; 
  WS_VISIBLE = 000000;

function GetWindowLong(hWnd: THandle; nIndex: Integer): LongInt;
  external 'GetWindowLongW@User32.dll stdcall';
function SetTimer(
  hWnd: longword; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord;
  external 'SetTimer@user32.dll stdcall';

var
  WasHidden: Boolean;

procedure HiddenTimerProc(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
var
  Hidden: Boolean;
  Style: LongInt;
begin
  Style := GetWindowLong(WizardForm.Handle, GWL_STYLE);
  Hidden := (Style and WS_VISIBLE) = 0;
  if Hidden and not WasHidden then
  begin
    Log('Minimized, stopping music...');
  end
    else
  if not Hidden and WasHidden then
  begin
    Log('Restored, resuming music...');
  end;
  WasHidden := Hidden;
end;

procedure InitializeWizard();
begin
  WasHidden := False;
  SetTimer(0, 0, 500, CreateCallback(@HiddenTimerProc));
end;

对于 CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback 库。

不幸的是,没有任何通知事件可以通知您 WizardForm 是否已最小化或已恢复,因为构建安装不需要此类事件。

如果您真的想检查 WizardForm 是否已还原或最小化(而不是检查其可见性),


首先您需要修改 Inno Setup 源代码,为向导提供最小化和恢复转换(动画)Window。

注意:以下源代码更改已通过 Inno Setup 5.5.9 Unicode 和 Ansi 版本成功测试。

  1. 从 Inno Setup 的安装程序中完全隐藏 Delphi 的隐藏申请表:

    Setup.exe > Setup.exe 的项目选项 > 应用程序 > 目标文件扩展名 > e32.

    右键单击 Setup.e32 > 查看源代码。

    像这样更改安装程序的 { Initialize ... 部分:

    在行前添加if shWindowVisible in SetupHeader.Options thenShowWindow(Application.Handle, SW_SHOW);.

    像这样更改安装程序的 { Run } 部分:

    ...
    { Run }
    try
      Application.MainFormOnTaskBar := False;
      Application.ShowMainForm := False;
      ShowWindow(Application.Handle, SW_HIDE);
      Application.Run;
    except
    ...
    

    大功告成!现在它将被隐藏。

    有关背景信息,请参阅

  2. 将最小化和恢复过渡(动画)添加到 Inno 设置向导表单:

    在单位向导中,

    改变 TWizardForm.CreateParams 像这样:

    procedure TWizardForm.CreateParams(var Params: TCreateParams);
    begin
      inherited;
      { Ensure the form is on top of MainForm by making MainForm
        the "parent" of the form when *MainForm is set to Visible*. }
      if shWindowVisible in SetupHeader.Options then
        Params.WndParent := MainForm.Handle
      else
        Params.WndParent := GetDesktopWindow;
    end;
    

    改变 TWizardForm.WMSysCommand 像这样:

    procedure TWizardForm.WMSysCommand(var Message: TWMSysCommand);
    begin
      if Message.CmdType and $FFF0 = SC_MINIMIZE then begin
        { A minimize button is shown on the wizard form when (shWindowVisible in
          SetupHeader.Options). When it is clicked we want to minimize the whole
          application. }
        if shWindowVisible in SetupHeader.Options then
          Application.Minimize
        else
          ShowWindow(WizardForm.Handle, SW_MINIMIZE);
      end
      else
      if Message.CmdType and $FFF0 = SC_RESTORE then begin
        if shWindowVisible in SetupHeader.Options then
          Application.Restore
        else
          ShowWindow(WizardForm.Handle, SW_RESTORE);
      end;
      if Message.CmdType = 9999 then
        MainForm.ShowAboutBox
      else
        inherited;
    end;
    

    声明一个名为 TWizardForm.FormShow 的新过程,如下所示:

    procedure FormShow(Sender: TObject);
    

    在单元向导的 Implementation 部分中如下声明。

    procedure TWizardForm.FormShow(Sender: TObject);
    begin
      if not(shWindowVisible in SetupHeader.Options) then
        ShowWindow(Application.Handle, SW_HIDE);
    end;
    

    最后,将此TWizardForm.FormShow 添加为 WizardForm 的 OnShow Form 事件。

    你快完成了!现在向导 Window 应该会按照您的预期显示恢复和最小化动画。

  3. 在将最小化过渡(动画)添加到 Inno 设置向导后修复 MessageBox Parent Window 问题:

    注意:必须这样做以防止记录的向导消息框(可以使用 Inno Setup 编译器日志记录)有时显示两个任务栏按钮,即使 WizardForm 是可见的。

    在 Unit Main 的 { Variables for command line parameters } 部分,声明一个新的布尔变量,如下所示:

    IsApplicationRunning: Boolean;
    

    主单元中,

    改变程序 AbortInit 像这样:

    procedure AbortInit(const Msg: TSetupMessageID);
    begin
      IsApplicationRunning := False;
      LoggedMsgBox(SetupMessages[Msg], '', mbCriticalError, MB_OK, True, IDOK);
      Abort;
    end;
    

    改变程序 AbortInitFmt1 像这样:

    procedure AbortInitFmt1(const Msg: TSetupMessageID; const Arg1: String);
    begin
      IsApplicationRunning := False;
      LoggedMsgBox(FmtSetupMessage(Msg, [Arg1]), '', mbCriticalError, MB_OK, True, IDOK);
      Abort;
    end;
    

    改变程序 AbortInitServicePackRequired 像这样:

    procedure AbortInitServicePackRequired(const ServicePack: Word);
    begin
      IsApplicationRunning := False;
      LoggedMsgBox(FmtSetupMessage(msgWindowsServicePackRequired, ['Windows', IntToStr(Hi(ServicePack))]), '', mbCriticalError, MB_OK, True, IDOK);
      Abort;
    end;
    

    在单位向导中,

    修改之前添加的程序 TWizardForm.FormShow 这样:

    procedure TWizardForm.FormShow(Sender: TObject);
    begin
      if not(shWindowVisible in SetupHeader.Options) then
        ShowWindow(Application.Handle, SW_HIDE);
      IsApplicationRunning := True;
    end;
    

    单元CmnFunc,

    WizardMain 添加到单元 CmnFunc 的 Implementation 中的使用部分。

    改变程序AppMessageBox像这样:

    function AppMessageBox(const Text, Caption: PChar; Flags: Longint): Integer;
    var
      ActiveWindow: HWND;
      MessageHandler: HWND;
      WindowList: Pointer;
    {$IFNDEF IS_D4}
      DidMove: Boolean;
      OldRect: TRect;
    {$ENDIF}
    begin
      if MessageBoxRightToLeft then
        Flags := Flags or (MB_RTLREADING or MB_RIGHT);
    
      if IsApplicationRunning = False then
        MessageHandler := Application.Handle
      else
        MessageHandler := WizardForm.Handle;
    
      { If the application window isn't currently visible, show the message box
        with no owner window so it'll get a taskbar button }
      if IsIconic(MessageHandler) or (GetWindowLong(MessageHandler, GWL_STYLE) and WS_VISIBLE = 0) or (GetWindowLong(MessageHandler, GWL_EXSTYLE) and WS_EX_TOOLWINDOW <> 0) then begin
        ActiveWindow := GetActiveWindow;
        WindowList := DisableTaskWindows(0);
        try
          { Note: DisableTaskWindows doesn't disable invisible windows.
            MB_TASKMODAL will ensure that Application.Handle gets disabled too. }
          Result := MessageBox(0, Text, Caption, Flags or MB_TASKMODAL);
        finally
          EnableTaskWindows(WindowList);
          SetActiveWindow(ActiveWindow);
        end;
        Exit;
      end;
    
      TriggerMessageBoxCallbackFunc(Flags, False);
      try
        {$IFDEF IS_D4}
        { On Delphi 4+, simply call Application.MessageBox }
        Result := Application.MessageBox(Text, Caption, Flags);
        {$ELSE}
        { Use custom implementation on Delphi 2 and 3. The Flags parameter is
          incorrectly declared as a Word on Delphi 2's Application.MessageBox, and
          there is no support for multiple monitors. }
        DidMove := MoveAppWindowToActiveWindowMonitor(OldRect);
        try
          ActiveWindow := GetActiveWindow;
          WindowList := DisableTaskWindows(0);
          try
            Result := MessageBox(Application.Handle, Text, Caption, Flags);
          finally
            EnableTaskWindows(WindowList);
            SetActiveWindow(ActiveWindow);
          end;
        finally
          if DidMove then
            SetWindowPos(Application.Handle, 0,
              OldRect.Left + ((OldRect.Right - OldRect.Left) div 2),
              OldRect.Top + ((OldRect.Bottom - OldRect.Top) div 2),
              0, 0, SWP_NOACTIVATE or SWP_NOREDRAW or SWP_NOSIZE or SWP_NOZORDER);
        end;
        {$ENDIF}
      finally
        TriggerMessageBoxCallbackFunc(Flags, True);
      end;
    end;
    

    现在所有已记录和任何其他消息框将正常显示!

    现在,使用自述文件中推荐的编译器编译安装程序 (Setup.e32),并将其复制到安装 Inno Setup 的目录。

    注意:Inno Setup Unicode 或 Ansi 的安装版本必须与您修改为重新编译的 Inno Setup 源代码的版本相匹配。或者您可以编译整个 Inno Setup 项目并复制 - 将这个修改和重新编译的安装程序 (Setup.e32) 替换到您编译 Inno Setup 的目录。

    重新编译安装程序后,您需要在 Pascal 脚本中添加以下代码。

    [Files]
    Source: "InnoCallback.dll"; Flags: dontcopy
    
    [Code]
    #ifdef UNICODE
      #define AW "W"
    #else
      #define AW "A"
    #endif
    
    const
      GWL_WNDPROC = -4;
      SC_ABOUTBOX = 9999;
      SC_RESTORE = $F120;
      SC_MINIMIZE = $F020;
      WM_SYSCOMMAND = 12;
    
    Type
      WPARAM = UINT_PTR;
      LPARAM = LongInt;
      LRESULT = LongInt;
      TWindowProc = function(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
    
    var
      PrevWndProc: LongInt;
    
    function CallWindowProc(lpPrevWndFunc: LongInt; hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
      external 'CallWindowProc{#AW}@user32.dll stdcall';
    function WrapWindowProc(Callback: TWindowProc; ParamCount: Integer): LongWord;
      external 'wrapcallback@files:InnoCallback.dll stdcall';
    function SetWindowLong(hWnd: HWND; nIndex: Integer; dwNewLong: LongInt): LongInt;
      external 'SetWindowLong{#AW}@user32.dll stdcall';
    
    function Wizard_WMSYSCOMMAND(hwnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
    begin
      if (uMsg = WM_SYSCOMMAND) and (wParam and $FFF0 = SC_MINIMIZE) then begin
        //SOMETHING LIKE BASS_Pause();.  
        Log('Wizard Window has been Minimized.'); 
      end;
      if (uMsg = WM_SYSCOMMAND) and (wParam and $FFF0 = SC_RESTORE) then begin
        //SOMETHING LIKE BASS_Start();. 
        Log('Wizard Window has been Restored.');
      end;
      if (uMsg = WM_SYSCOMMAND) and (wParam = SC_ABOUTBOX) then begin
        Result := 0;
        MainForm.ShowAboutBox;
      end
      else
        Result := CallWindowProc(PrevWndProc, hwnd, uMsg, wParam, lParam);
    end;
    
    procedure InitializeWizard();
    begin
      PrevWndProc := SetWindowLong(WizardForm.Handle, GWL_WNDPROC, WrapWindowProc(@Wizard_WMSYSCOMMAND, 4));
    end;
    
    procedure DeinitializeSetup();
    begin
      SetWindowLong(WizardForm.Handle, GWL_WNDPROC, PrevWndProc);
    end;
    

    现在,当 WizardForm 通过编译器日志消息最小化或恢复时,您应该会立即收到通知。您可以根据您的代码暂停或恢复音乐,方法是将它们添加到函数 Wizard_WMSYSCOMMAND.