I would like to be able to disable the selection of a component based on a specific component being selected. I cannot do this through nesting of components, as the component needs to be selectable on it's own, but not if another specific component is selected. Currently I handle this using the NextButtonClick event displaying a message:
if IsComponentSelected('Client') and IsComponentSelected('Sync') then
begin
MsgBox('A Client installation cannot have the Synchronisation component selected.',
mbError, MB_OK);
Result := False;
end;
which prevents the user from continuing until they deselect the incompatible combination. However, it would be far more elegant if I could simply disable the selection of the component rather than displaying a message:
if CurPageID = wpSelectComponents then
begin
if IsComponentSelected('Client') then
begin
WizardForm.ComponentsList.Checked[15] := False;
WizardForm.ComponentsList.ItemEnabled[15] := False;
end
else
begin
WizardForm.ComponentsList.ItemEnabled[15] := True;
end;
end;
The problem is there doesn't seem to be an event that allows this to change dynamically as the user selects or deselects components. Is there an event I can place this code in or another way to do this?
You had TLama's solution which was probably better than the following, but still this code is also a way to achieve the goal (though you will need innocallback.dll):
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Files]
Source: ".\InnoCallback.dll"; DestDir: "{tmp}"; Flags: dontcopy nocompression
[Components]
Name: "Client"; Description: "Client"; Types: custom
Name: "Sync"; Description: "Sync"; Types: custom
[Code]
var
TimerID: Integer;
type
TTimerProc = procedure(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
external 'wrapcallback#files:InnoCallback.dll stdcall';
function SetTimer(hWnd: HWND; nIDEvent, uElapse: UINT;
lpTimerFunc: UINT): UINT; external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL;
external 'KillTimer#user32.dll stdcall';
procedure KillComponentsTimer;
begin
if TimerID <> 0 then
begin
if KillTimer(0, TimerID) then
TimerID := 1;
end;
end;
procedure OnComponentsCheck(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
begin
if IsComponentSelected('Client') then begin
WizardForm.ComponentsList.Checked[1] := False;
WizardForm.ComponentsList.ItemEnabled[1] := False;
end
else begin
WizardForm.ComponentsList.ItemEnabled[1] := True;
end;
if IsComponentSelected('Sync') then begin
WizardForm.ComponentsList.Checked[0] := False;
WizardForm.ComponentsList.ItemEnabled[0] := False;
end
else begin
WizardForm.ComponentsList.ItemEnabled[0] := True;
end;
end;
procedure ComponentsCheck();
var
TimerCallback: LongWord;
begin
TimerCallback := WrapTimerProc(#OnComponentsCheck, 4);
TimerID := SetTimer(0, 0, 100, TimerCallback);
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectComponents then
begin
ComponentsCheck;
end;
if CurPageID = not wpSelectComponents then
begin
KillComponentsTimer;
end;
end;
The code I used in the end is based entirely on #TLama's code that he provided previously as this does not require the use of an external DLL.
[Code]
const
//Define global constants
CompIndexSync = 15;
CompIndexSyncClient = 16;
CompIndexSyncServer = 17;
var
//Define global variables
CompPageVisited: Boolean;
DefaultCompClickCheck: TNotifyEvent;
DefaultCompTypeChange: TNotifyEvent;
//Uncheck and set the enabled state of the Sync components based on whether the Client component is selected
procedure UpdateComponents;
begin
with WizardForm.ComponentsList do
begin
if IsComponentSelected('Client') then
begin
CheckItem(CompIndexSync, coUncheck);
end;
ItemEnabled[CompIndexSync] := not IsComponentSelected('Client');
ItemEnabled[CompIndexSyncClient] := not IsComponentSelected('Client');
ItemEnabled[CompIndexSyncServer] := not IsComponentSelected('Client');
Invalidate; //required for text state to update correctly
end;
end;
//Update the component states if the component states change and restore the original event handler procedures
procedure ComponentsClickCheck(Sender: TObject);
begin
DefaultCompClickCheck(Sender);
UpdateComponents;
end;
procedure ComponentsTypesComboChange(Sender: TObject);
begin
DefaultCompTypeChange(Sender);
UpdateComponents;
end;
procedure InitializeWizard();
begin
//Store the original Components Page OnClickCheck and Types Combo Box OnChange event procedures and assign custom procedures
DefaultCompClickCheck := WizardForm.ComponentsList.OnClickCheck;
WizardForm.ComponentsList.OnClickCheck := #ComponentsClickCheck;
DefaultCompTypeChange := WizardForm.TypesCombo.OnChange;
WizardForm.TypesCombo.OnChange := #ComponentsTypesComboChange;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
//Update the Components Page if entered for the first time
if (CurPageID = wpSelectComponents) and not CompPageVisited then
begin
CompPageVisited := True;
UpdateComponents;
end;
end;
Although this and #RobeN's solutions both work, they both exhibit a graphical update issue i.e. when changing the status of the components from active to disabled and vice versa, the text doesn't automatically dim or undim until the scroll bar is dragged.
Note that the refresh issue described above is resolved by adding an `Invalidate;' line. Code updated above with a comment to reflect this. See Inno Setup Components graphical refresh issue for further information. Thanks #TLama.
Related
I have prepared simple script that displays image under ProgressGauge bar on wpInstalling Page.
But... I need more complex functionality.
What I need is multiple images show, each after X (e.g. 7) seconds (with loop when installation longer then X secs * number of images) or each after X (e.g. 10) percent of installation. I have tried to embed images display in ProgressGauge.Position, but I failed.
Here is what I have:
procedure CurPageChanged(CurPageID: Integer);
var
BmpFile: TBitmapImage;
begin
ExtractTemporaryFile('01.bmp');
ExtractTemporaryFile('02.bmp');
ExtractTemporaryFile('03.bmp');
if CurPageID = wpInstalling then
begin
BmpFile:= TBitmapImage.Create(WizardForm);
BmpFile.Bitmap.LoadFromFile(ExpandConstant('{tmp}\01.bmp'));
BmpFile.Width:= ScaleX(420);
BmpFile.Height:= ScaleY(180);
BmpFile.Left := WizardForm.ProgressGauge.Left + ScaleX(0);
BmpFile.Top := WizardForm.ProgressGauge.Top + ScaleY(35);
// BmpFile.Parent:= WizardForm.InstallingPage;
// BmpFile:= TBitmapImage.Create(WizardForm);
// BmpFile.Bitmap.LoadFromFile(ExpandConstant('{tmp}\03.bmp'));
// BmpFile.Width:= ScaleX(420);
// BmpFile.Height:= ScaleY(400);
// BmpFile.Left := WizardForm.ProgressGauge.Left + ScaleX(0);
// BmpFile.Top := WizardForm.ProgressGauge.Top + ScaleY(35);
// BmpFile.Parent:= WizardForm.InstallingPage;
// BmpFile:= TBitmapImage.Create(WizardForm);
// BmpFile.Bitmap.LoadFromFile(ExpandConstant('{tmp}\03.bmp'));
// BmpFile.Width:= ScaleX(420);
// BmpFile.Height:= ScaleY(400);
// BmpFile.Left := WizardForm.ProgressGauge.Left + ScaleX(0);
// BmpFile.Top := WizardForm.ProgressGauge.Top + ScaleY(35);
// BmpFile.Parent:= WizardForm.InstallingPage;
end;
end;
The goal is:
On the wpInstalling there should be X images displayed, every next per X seconds or after X percent of installation.
Since the ProgressGauge has no progress change events and there is no way to process setup application messages you will need to use the Windows API timer. This timer needs a callback function which you can't define in Inno Setup script unfortunately so you will need some external library to do this job for you. However there's the InnoCallback library which can do exactly this.
For the following code copy the InnoCallback.dll library into your setup directory, merge this code with your Inno Setup script and implement some kind of a slideshow page turning in the OnSlideTimer event which will be called periodically (with the current settings each second).
[Files]
Source: "InnoCallback.dll"; DestDir: "{tmp}"; Flags: dontcopy
[code]
var
TimerID: Integer;
type
TTimerProc = procedure(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
external 'wrapcallback#files:InnoCallback.dll stdcall';
function SetTimer(hWnd: HWND; nIDEvent, uElapse: UINT;
lpTimerFunc: UINT): UINT; external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL;
external 'KillTimer#user32.dll stdcall';
procedure OnSlideTimer(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR;
SysTime: DWORD);
begin
{ here you can turn your slideshow pages; use some variable to store the }
{ current index of the slide you are on, note that this procedure is called }
{ periodically each 1000 ms (see below why), so here you can also check the }
{ progress value, if you want to }
end;
procedure StartSlideTimer;
var
TimerCallback: LongWord;
begin
TimerCallback := WrapTimerProc(#OnSlideTimer, 4);
{ third parameter here is the timer's timeout value in milliseconds }
TimerID := SetTimer(0, 0, 1000, TimerCallback);
end;
procedure KillSlideTimer;
begin
if TimerID <> 0 then
begin
if KillTimer(0, TimerID) then
TimerID := 0;
end;
end;
function InitializeSetup: Boolean;
begin
Result := True;
TimerID := 0;
end;
procedure DeinitializeSetup;
begin
KillSlideTimer;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpInstalling then
StartSlideTimer
else
KillSlideTimer;
end;
How to close the installer on the "Finished" page after a certain time?
It could also be interpreted as: how to close the installer after some time of non-activity? (close/cancel install). Is this possible?
Setup a timer once the "Finished" page displays to trigger the close.
[Code]
function SetTimer(hWnd, nIDEvent, uElapse, lpTimerFunc: LongWord): LongWord;
external 'SetTimer#User32.dll stdcall';
function KillTimer(hWnd, nIDEvent: LongWord): LongWord;
external 'KillTimer#User32.dll stdcall';
var
PageTimeoutTimer: LongWord;
PageTimeout: Integer;
procedure UpdateFinishButton;
begin
WizardForm.NextButton.Caption :=
Format(SetupMessage(msgButtonFinish) + ' - %ds', [PageTimeout]);
end;
procedure PageTimeoutProc(
H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
begin
if PageTimeout > 1 then
begin
Dec(PageTimeout);
UpdateFinishButton;
end
else
begin
WizardForm.NextButton.OnClick(WizardForm.NextButton);
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
begin
PageTimeout := 10;
UpdateFinishButton;
PageTimeoutTimer := SetTimer(0, 0, 1000, CreateCallback(#PageTimeoutProc));
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpFinished then
begin
KillTimer(0, PageTimeoutTimer);
PageTimeoutTimer := 0;
end;
Result := True;
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.
Related questions:
MsgBox - Make unclickable OK Button and change to countdown - Inno Setup;
Inno Setup - Automatically submitting uninstall prompts.
How to change OK button into a countdown timer like this...
5,4,3,2,1,0 [MsgBox Closes automatically] don't display 0
Proceeds to setup procedure.
The button should not be clickable and faded. Like inactive window/button/box.
this one:
not this one:
I use the code code from this question: How to show a message box for a specified time?
There's no built-in function with such functionality. Neither in Inno Setup, nor WinAPI.
You have to implement the dialog on your own and use a timer to implement the count down.
[Code]
function SetTimer(hWnd: LongWord; nIDEvent, uElapse: LongWord;
lpTimerFunc: LongWord): LongWord; external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: LongWord): BOOL;
external 'KillTimer#user32.dll stdcall';
var
CountdownButton: TNewButton;
Countdown: Integer;
procedure UpdateCountDownButtonCaption;
begin
CountdownButton.Caption := Format('%d sec', [Countdown]);
end;
procedure CountdownProc(
H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
begin
Dec(Countdown);
if Countdown = 0 then
begin
CountdownButton.Enabled := True;
TForm(CountdownButton.Parent).Close;
end
else
begin
UpdateCountDownButtonCaption;
end;
end;
procedure CountdownMessageBoxCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
{ Prevent the dialog from being close by the X button and Alt-F4 }
CanClose := CountdownButton.Enabled;
end;
procedure CountdownMessageBox(Message: string; Seconds: Integer);
var
Form: TSetupForm;
MessageLabel: TLabel;
Timer: LongWord;
begin
Form := CreateCustomForm;
try
Form.ClientWidth := ScaleX(256);
Form.ClientHeight := ScaleY(96);
Form.Caption := 'Information';
Form.Position := poMainFormCenter;
Form.OnCloseQuery := #CountdownMessageBoxCloseQuery;
MessageLabel := TLabel.Create(Form);
MessageLabel.Top := ScaleY(16);
MessageLabel.Left := ScaleX(16);
MessageLabel.AutoSize := True;
MessageLabel.Caption := Message;
MessageLabel.Parent := Form;
if CountdownButton <> nil then
RaiseException('Countdown in progress already');
Countdown := Seconds;
CountdownButton := TNewButton.Create(Form);
CountdownButton.Parent := Form;
CountdownButton.Width := ScaleX(88);
CountdownButton.Height := ScaleY(26);
CountdownButton.Left :=
Form.ClientWidth - CountdownButton.Width - ScaleX(18);
CountdownButton.Top :=
Form.ClientHeight - CountdownButton.Height - ScaleX(11);
UpdateCountDownButtonCaption;
CountdownButton.Name := 'CountdownButton';
CountdownButton.ModalResult := mrOk;
CountdownButton.Default := True;
CountdownButton.Enabled := False;
Timer := SetTimer(0, 0, 1000, CreateCallback(#CountdownProc));
try
Form.ShowModal();
finally
KillTimer(0, Timer);
end;
finally
Form.Free();
CountdownButton := nil;
end;
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.
Use it like:
CountdownMessageBox('Message here', 10);
Related questions:
How to show a message box for a specified time?
Inno Setup - How to close finished installer after a certain time?
Inno Setup - Automatically submitting uninstall prompts.
Here's the code...
ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
MB_TIMEDOUT = 32000;
MB_ICONERROR = $10;
MB_ICONQUESTION = $20;
MB_ICONWARNING = $30;
MB_ICONINFORMATION = $40;
function MessageBoxTimeout(hWnd: HWND; lpText: string; lpCaption: string;
uType: UINT; wLanguageId: Word; dwMilliseconds: DWORD): Integer;
external 'MessageBoxTimeout{#AW}#user32.dll stdcall';
procedure InitializeWizard;
begin
MessageBoxTimeout(WizardForm.Handle, 'Some ' +
'message', 'Setup', MB_OK or MB_ICONINFORMATION, 0, 5000);
end;
I just want the message box to appear without the button. What code to be added or removed? Where would I insert it? Thanks!
Does this a code from How to disable the “Next” button on the wizard form in Inno Setup? work with my script? I can't seem to to make it working.
You cannot.
But as you already know from MsgBox - Make unclickable OK Button and change to countdown - Inno Setup, you can implement the message box from a scratch yourself. This way, you can customize it any way you want.
Actually, all you need is to remove the button from my answer to the above question.
[Code]
function SetTimer(hWnd: LongWord; nIDEvent, uElapse: LongWord;
lpTimerFunc: LongWord): LongWord; external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: LongWord): BOOL;
external 'KillTimer#user32.dll stdcall';
var
TimeoutForm: TSetupForm;
procedure TimeoutProc(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
begin
TimeoutForm.Tag := TimeoutForm.Tag - 1;
if TimeoutForm.Tag = 0 then
begin
TimeoutForm.Close;
end;
end;
procedure TimeoutMessageBoxCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
{ Prevent the dialog from being closed by the X button and Alt-F4 }
CanClose := (TimeoutForm.Tag = 0);
end;
procedure TimeoutMessageBox(Message: string; Seconds: Integer);
var
MessageLabel: TLabel;
Timer: LongWord;
begin
TimeoutForm := CreateCustomForm;
try
TimeoutForm.ClientWidth := ScaleX(256);
TimeoutForm.ClientHeight := ScaleY(64);
TimeoutForm.Caption := 'Information';
TimeoutForm.Position := poMainFormCenter;
TimeoutForm.OnCloseQuery := #TimeoutMessageBoxCloseQuery;
TimeoutForm.Tag := Seconds;
MessageLabel := TLabel.Create(TimeoutForm);
MessageLabel.Top := ScaleY(16);
MessageLabel.Left := ScaleX(16);
MessageLabel.AutoSize := True;
MessageLabel.Caption := Message;
MessageLabel.Parent := TimeoutForm;
Timer := SetTimer(0, 0, 1000, CreateCallback(#TimeoutProc));
try
TimeoutForm.ShowModal();
finally
KillTimer(0, Timer);
end;
finally
TimeoutForm.Free();
TimeoutForm := nil;
end;
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.
I'm trying to override Next/Cancel buttons on wpFinished page - NextButton should show downloaded file and exit the installator - it's working ok but CancelButton doesn't do nothing - it should close the installator with standard confirm. I wonder it is possible with standard inno events or I need to write own code to exit the application and show the confirm?
function NextButtonClick(CurPage: Integer): Boolean;
begin
if CurPage = wpFinished then begin
ShowDownloadedFile();
end;
Result := True;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then begin
WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall);
WizardForm.CancelButton.Caption := SetupMessage(msgButtonFinish);
WizardForm.CancelButton.Visible := True;
end;
end;
Here it is, but don't do this at home kids :-)
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
procedure ExitProcess(uExitCode: UINT);
external 'ExitProcess#kernel32.dll stdcall';
function NextButtonClick(CurPage: Integer): Boolean;
begin
Result := True;
// if the fake Finish button was clicked...
if CurPage = wpFinished then
MsgBox('Welcome to the next installation!', mbInformation, MB_OK);
end;
procedure CancelButtonClickFinishedPage(Sender: TObject);
begin
// display the "Exit Setup ?" message box and if the user selects "Yes",
// then exit the process; it is currently the only way how to exit setup
// process manually
if ExitSetupMsgBox then
ExitProcess(0);
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
begin
WizardForm.NextButton.Caption := SetupMessage(msgButtonInstall);
WizardForm.CancelButton.Caption := SetupMessage(msgButtonFinish);
WizardForm.CancelButton.Visible := True;
// bind your own OnClick event for the Cancel button; the original one
// is already disconnected at this stage
WizardForm.CancelButton.OnClick := #CancelButtonClickFinishedPage;
end;
end;
The proper alternative to what you're trying to do is to include a [Run] entry like so:
[Run]
Filename: {app}\yourfile.exe; Description: Run my application; Flags: postinstall nowait
This will display a checkbox on the wpFinished page giving them the choice to run the app or not.