【问题标题】:Inno Setup Detect (get notified) when WizardForm minimizes and restores当 WizardForm 最小化和恢复时 Inno Setup Detect(得到通知)
【发布时间】:2016-09-28 05:01:27
【问题描述】:

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

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

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

提前致谢。

【问题讨论】:

    标签: inno-setup pascalscript


    【解决方案1】:

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


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

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

    [Code]
    
    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';
    
    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,你需要 Inno Setup 6。如果你被 Inno Setup 5 卡住,你可以使用 InnoTools InnoCallback 库中的 WrapCallback 函数。

    【讨论】:

      【解决方案2】:

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

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


      首先您需要修改 Inno Setup Source Code 以最小化和恢复向导窗口的过渡(动画)。

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

      1. 从 Inno Setup 的安装程序中完全隐藏 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 Form 事件。

        你快完成了!现在,Wizard Window 应该会按您的预期显示“Restore and Minimize Animations”。

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

        注意:必须这样做以防止已记录的向导消息框(可以使用 Inno Setup Compiler Log 记录)有时显示两个任务栏按钮,即使 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 中的 Uses 部分。

        改变程序 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 = $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 中添加它们来根据您的代码暂停或恢复音乐。

      【讨论】:

      • 认真思考是找到解决方案的最重要方法。 ;-)
      • Wizard_WMSYSCOMMAND 中,你应该用$FFF0 屏蔽wParam,就像在TWizardForm.WMSysCommand 中一样。
      • 你的意思是像(wParam = $FFF0 and SC_MINIMIZE)?如果不是,它会是怎样的?
      • 没有wParam and $FFF0 = SC_MINIMIZE
      • 是现在吗?改变它后我没有注意到任何特别之处。为什么它需要像你说的那样改变?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-28
      • 1970-01-01
      • 1970-01-01
      • 2012-05-30
      相关资源
      最近更新 更多