当WizardForm最小化并恢复时,Inno Setup Detect(获得通知)

时间:2016-09-28 05:01:27

标签: inno-setup pascalscript

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

我想知道如何检测WizardForm是否被最小化或恢复,并根据WizardForm的窗口状态暂停或启动BASS音乐播放。 (使用类似BASS_PauseBASS_StopBASS_Start.

的功能

我应该如何以及如何选择这样做? TWindowStateWMSYSCOMMAND?

提前致谢。

2 个答案:

答案 0 :(得分:2)

我认为在最小化或恢复向导表单时,没有任何事件会通知您。 Inno Setup Pascal Script中没有TForm.WindowState

但是你可以安排一个频繁的计时器(使用InnoCallback DLL)并检查表单状态的变化。

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

[Files]
Source: "InnoCallback.dll"; Flags: dontcopy

[Code]

type
  TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);

const
  GWL_STYLE = -16; 
  WS_VISIBLE = $10000000;

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';

function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
  external 'wrapcallback@files:innocallback.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();
var
  HiddenTimerCallback: LongWord;
begin
  HiddenTimerCallback := WrapTimerProc(@HiddenTimerProc, 4);
  WasHidden := False;
  SetTimer(0, 0, 500, HiddenTimerCallback);
end;

答案 1 :(得分:2)

不幸的是,没有任何通知事件可以通知您WizardForm是否被最小化或恢复,因为这样的事件不是构建安装所必需的。

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

首先,您需要修改Inno设置源代码,以便为向导窗口提供最小化和恢复过渡(动画)。

注意:使用Inno Setup 5.5.9 Unicode和Ansi版本成功测试了以下源代码更改。

  1. 完全隐藏了Inno安装程序设置程序中隐藏的Delphi隐藏申请表:

    Setup.exe> Setup.exe的项目选项>申请>目标文件扩展名> E32。

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

    更改安装程序的{ Initialize ...部分,如下所示:

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

    更改安装程序的{ Run }部分,如下所示:

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

    你完成了!现在它将被隐藏。

    有关背景信息,请参阅Setup Programs created using Inno Setup Compiler doesn't display Minimize Animation

  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表单事件。

    你差不多完成了!现在,向导窗口应该按预期显示“还原”和“最小化动画”。

  3. 在向Inno设置向导添加最小化过渡(动画)后修复MessageBox父窗口问题:

    注意:必须这样做是为了防止记录向导消息框(可以使用Inno安装程序编译器日志记录的消息框)有时显示两个任务栏按钮,即使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添加到Unit 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;
    

    现在所有Logged和任何其他消息框都会正常显示!

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

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

    重新编译安装程序后,需要将以下代码添加到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 = $0112;
    
    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功能中。