2
votes

I am in the process of adding a BASS Audio Project in my Pascal Script. Adding a music playback for a very long installation is not bad for the user. But it is good to stop the music when user minimizes the WizardForm to taskbar. And automatically start music if user restores it again from taskbar.

I like to know how can I detect if WizardForm is minimized or restored and pause or start BASS music playing according to WizardForm's window state. (Using functions like like BASS_Pause,BASS_Stop or BASS_Start.)

How and what should I choose to do this? TWindowState or WMSYSCOMMAND?

Thanks in advance.

2

2 Answers

2
votes

I do not think there's any event that will notify you when the wizard form is minimized or restored. The TForm.WindowState is not available in the Inno Setup Pascal Script either.


But you can schedule a frequent timer and check for changes in the form state.

Note the the form is actually hidden, not minimized, when you click the minimize button (there's no minimize animation). So use the GetWindowLong WinAPI function to check for the WS_VISIBLE window style.

[Code]

const
  GWL_STYLE = -16; 
  WS_VISIBLE = $10000000;

function GetWindowLong(hWnd: THandle; nIndex: Integer): LongInt;
  external '[email protected] stdcall';
function SetTimer(
  hWnd: longword; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord;
  external '[email protected] 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;

For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.

2
votes

Unfortunately, there is no any Notify Event that can inform you if WizardForm is Minimized or Restored because such Event is not necessary to build Installations.

If you Really want to check if WizardForm is Restored or Minimized (Not to check its Visibility),


First you need to modify Inno Setup Source Code to give Minimize and Restore Transitions (Animations) for Wizard Window.

Note: the following Source Code Changes are successfully tested with Inno Setup 5.5.9 Unicode and Ansi Versions.

  1. Hiding Hidden Application Form of Delphi from Inno Setup's Setup Program completely:

    Setup.exe > Project Options for Setup.exe > Application > Target File Extension > e32.

    Right Click Setup.e32 > View Source.

    Change the { Initialize ... Section of Setup Program like this:

    Add if shWindowVisible in SetupHeader.Options then before the line ShowWindow(Application.Handle, SW_SHOW);.

    Change the { Run } Section of Setup Program like this:

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

    You're done! Now it will be hidden.

    For a background information, see Setup Programs created using Inno Setup Compiler doesn't display Minimize Animation.

  2. Adding Minimize and Restore Transitions (Animations) to Inno Setup Wizard Form:

    In Unit Wizard,

    Change TWizardForm.CreateParams like this:

    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;
    

    Change TWizardForm.WMSysCommand like this:

    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;
    

    Declare a new procedure called TWizardForm.FormShow like shown below:

    procedure FormShow(Sender: TObject);
    

    Declare it like below in Implementation Section of Unit Wizard.

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

    Finally, add this TWizardForm.FormShow as OnShow Form Event of the WizardForm.

    You're almost done! Now Wizard Window should display Restore and Minimize Animations as you expect.

  3. Fixing MessageBox Parent Window issue after adding Minimize Transitions (Animations) to the Inno Setup Wizard:

    Note: This must be done to prevent Logged Wizard Message Boxes (Whose can be logged with Inno Setup Compiler Log) from sometimes displaying two Taskbar Buttons even WizardForm is Visible.

    In the Section { Variables for command line parameters } of Unit Main, declare a new Boolean Variable like this:

    IsApplicationRunning: Boolean;
    

    In Unit Main,

    Change the procedure AbortInit like this:

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

    Change the procedure AbortInitFmt1 like this:

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

    Change the procedure AbortInitServicePackRequired like this:

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

    In Unit Wizard,

    Modify the previously added procedure TWizardForm.FormShow like this:

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

    In Unit CmnFunc,

    Add Wizard and Main to the Uses Section in Implementation of Unit CmnFunc.

    Change the procedure AppMessageBox like this:

    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;
    

    Now all Logged and any other Message Boxes will be displayed Normally!

    Now, Compile the Setup Program (Setup.e32) using a Recommended Compiler in the ReadMe File and copy it to the directory where you installed Inno Setup.

    Note: The installed version of Inno Setup Unicode or Ansi must match the Version of the source code of Inno Setup which you're modified to Re-compile. Or you can Compile whole Inno Setup Project and Copy - Replace this modified and Re-compiled Setup Program (Setup.e32) to the directory where you compiled Inno Setup.

    After Re-compiling Setup Program, you need to add following codes to the Pascal Script.

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

    Now, you should be notified immediately when WizardForm Minimizes or Restores via Compiler Log Messages. You can Pause or Resume Music according to your code by adding them in the function Wizard_WMSYSCOMMAND.