Merging event function (InitializeWizard) implementations from different sources - inno-setup

I am now combining the script that I want but it has an error.
When I put a period, it will run but missing other feature.
Here is my code:
procedure InitializeWizard;
begin
MessageBoxTimeout(WizardForm.Handle, 'MsgBox ' +
Timeout 'Setup', MB_OK or MB_ICONINFORMATION, 0, 2000);
end;
var
TuneLabel: TLabel;
begin
ExtractTemporaryFile('tune.xm');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundCtrlButton := TNewButton.Create(WizardForm);
Music := BASS_MusicLoad(False,
ExpandConstant('{tmp}\tune.xm'), 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP, 0);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 10000);
BASS_ChannelPlay(Music, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 10;
SoundCtrlButton.TabStop := False;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 9;
SoundCtrlButton.Width := 40;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
TuneLabel := TLabel.Create(WizardForm);
TuneLabel.Parent := WizardForm;
TuneLabel.Caption := 'Tune';
TuneLabel.Left := SoundCtrlButton.Left + SoundCtrlButton.Width + ScaleX(5);
TuneLabel.Top :=
SoundCtrlButton.Top + ((SoundCtrlButton.Height - TuneLabel.Height) div 2);
end;
end;
The error refers to a line after the last end;.
Please help me through.

When you are reusing various feature implementations from different sources, those commonly implement the same Inno Setup event functions (like the InitializeWizard).
The solution for Inno Setup 6 is very simple, as shown below. In older versions it's more complicated. See lower.
Inno Setup 6
Inno Setup 6 has event attributes features that helps solving this problem.
Just make sure that each of your event implementation have an unique name, e.g. appending unique suffix. And add event attribute with the name of the implemented event.
[Code]
procedure InitializeWizard;
begin
Log('InitializeWizard called');
end;
<event('InitializeWizard')>
procedure InitializeWizard2;
begin
Log('InitializeWizard2 called');
end;
Inno Setup 5
In old versions of Inno Setup that does not support the event attributes, you have to merge these event functions as there can be just one function implementation.
You can do that by appending unique suffix to the different implementation and than calling them from a main implementation.
The main implementation have to be below the other implementations.
For example, if one source has InitializeWizard event function implemented as:
var
GlobalVariable1: Integer;
procedure SubProcedure1;
begin
{ blah }
end;
procedure InitializeWizard;
var
Variable1: Integer;
Variable2: Integer;
begin
Variable1 := GlobalVariable1;
SubProcedure1;
end;
And the other source as:
var
GlobalVariableA: Integer;
procedure SubProcedureA;
begin
{ blah }
end;
procedure InitializeWizard;
var
VariableA: Integer;
begin
VariableA := GlobalVariableA;
SubProcedureA;
end;
Then merged code should be:
var
GlobalVariable1: Integer;
procedure SubProcedure1;
begin
{ blah }
end;
procedure InitializeWizard1;
var
Variable1: Integer;
Variable2: Integer;
begin
Variable1 := GlobalVariable1;
SubProcedure1;
end;
var
GlobalVariableA: Integer;
procedure SubProcedureA;
begin
{ blah }
end;
procedure InitializeWizard2;
var
VariableA: Integer;
begin
VariableA := GlobalVariableA;
SubProcedureA;
end;
procedure InitializeWizard;
begin
InitializeWizard1;
InitializeWizard2;
end;
See also Inno Setup - Merging implementations of event functions that return boolean (like InitializeSetup).
So, in your specific case the code should be:
procedure InitializeWizard1;
begin
MessageBoxTimeout(WizardForm.Handle, 'MsgBox ' +
Timeout 'Setup', MB_OK or MB_ICONINFORMATION, 0, 2000);
end;
procedure InitializeWizard2;
var
TuneLabel: TLabel;
begin
ExtractTemporaryFile('tune.xm');
if BASS_Init(-1, 44100, 0, 0, 0) then
begin
SoundCtrlButton := TNewButton.Create(WizardForm);
Music := BASS_MusicLoad(False,
ExpandConstant('{tmp}\tune.xm'), 0, 0,
EncodingFlag or BASS_SAMPLE_LOOP, 0);
BASS_SetConfig(BASS_CONFIG_GVOL_STREAM, 10000);
BASS_ChannelPlay(Music, False);
SoundCtrlButton := TNewButton.Create(WizardForm);
SoundCtrlButton.Parent := WizardForm;
SoundCtrlButton.Left := 10;
SoundCtrlButton.TabStop := False;
SoundCtrlButton.Top := WizardForm.ClientHeight -
SoundCtrlButton.Height - 9;
SoundCtrlButton.Width := 40;
SoundCtrlButton.Caption :=
ExpandConstant('{cm:SoundCtrlButtonCaptionSoundOff}');
SoundCtrlButton.OnClick := #SoundCtrlButtonClick;
TuneLabel := TLabel.Create(WizardForm);
TuneLabel.Parent := WizardForm;
TuneLabel.Caption := 'Tune';
TuneLabel.Left := SoundCtrlButton.Left + SoundCtrlButton.Width + ScaleX(5);
TuneLabel.Top :=
SoundCtrlButton.Top + ((SoundCtrlButton.Height - TuneLabel.Height) div 2);
end;
end;
procedure InitializeWizard;
begin
InitializeWizard1;
InitializeWizard2;
end;
If you are using Inno Setup Script #Includes (ISSI), see Implementing event functions InitializeWizard while using ISSI (to add background image) in Inno Setup: Duplicate identifier 'INITIALIZEWIZARD'.

Related

Inno Setup - Define music button and error with language selector?

This is the code with the error:
#include "Music\botva2.iss"
#include "Music\BASS_Module.iss"
[Code]
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;
lpParameters: string; lpDirectory: string; nShowCmd: Integer): THandle;
external 'ShellExecuteW#shell32.dll stdcall';
var
LanguageForm: TSetupForm;
SelectLabel: TNewStaticText;
CancelButton: TNewButton;
procedure LangChange(Sender : TObject);
begin
case TNewComboBox(Sender).ItemIndex of
0: { English }
begin
SelectLabel.Caption := 'Select the language to the installation:';
CancelButton.Caption := 'Cancel';
LanguageForm.Caption := 'PH';
end;
1: { Español }
begin
SelectLabel.Caption := 'Selecciona el idioma de la instalación:';
CancelButton.Caption := 'Cancelar';
LanguageForm.Caption := 'PH';
end;
end;
end;
procedure SelectLanguage();
var
OKButton: TNewButton;
LangCombo: TNewComboBox;
Languages: TStrings;
Params: string;
Instance: THandle;
P, I: Integer;
S, L: string;
begin
Languages := TStringList.Create();
Languages.Add('eng=English');
Languages.Add('spa=Español');
LanguageForm := CreateCustomForm;
LanguageForm.Caption := SetupMessage(msgSelectLanguageTitle);
LanguageForm.ClientWidth := ScaleX(240);
LanguageForm.ClientHeight := ScaleY(125);
LanguageForm.BorderStyle := bsDialog;
LanguageForm.Center;
CancelButton := TNewButton.Create(LanguageForm);
CancelButton.Parent := LanguageForm;
CancelButton.Left := ScaleX(140);
CancelButton.Top := ScaleY(93);
CancelButton.Width := ScaleY(90);
CancelButton.Height := ScaleY(23);
CancelButton.TabOrder := 3;
CancelButton.ModalResult := mrCancel;
CancelButton.Caption := SetupMessage(msgButtonCancel);
OKButton := TNewButton.Create(LanguageForm);
OKButton.Parent := LanguageForm;
OKButton.Left := ScaleX(10);
OKButton.Top := ScaleY(93);
OKButton.Width := ScaleX(90);
OKButton.Height := ScaleY(23);
OKButton.Caption := SetupMessage(msgButtonOK);
OKButton.Default := True
OKButton.ModalResult := mrOK;
OKButton.TabOrder := 2;
LangCombo := TNewComboBox.Create(LanguageForm);
LangCombo.Parent := LanguageForm;
LangCombo.Left := ScaleX(16);
LangCombo.Top := ScaleY(56);
LangCombo.Width := ScaleX(206);
LangCombo.Height := ScaleY(21);
LangCombo.Style := csDropDownList;
LangCombo.DropDownCount := 16;
LangCombo.TabOrder := 1;
SelectLabel := TNewStaticText.Create(LanguageForm);
SelectLabel.Parent := LanguageForm;
SelectLabel.Left := ScaleX(16);
SelectLabel.Top := ScaleY(15);
SelectLabel.Width := ScaleX(273);
SelectLabel.Height := ScaleY(39);
SelectLabel.AutoSize := False
SelectLabel.Caption := SetupMessage(msgSelectLanguageLabel);
SelectLabel.TabOrder := 0;
SelectLabel.WordWrap := True;
for I := 0 to Languages.Count - 1 do
begin
P := Pos('=', Languages.Strings[I]);
L := Copy(Languages.Strings[I], 0, P - 1);
S := Copy(Languages.Strings[I], P + 1, Length(Languages.Strings[I]) - P);
LangCombo.Items.Add(S);
if L = ActiveLanguage then
LangCombo.ItemIndex := I;
LangCombo.OnChange := #LangChange;
end;
if LanguageForm.ShowModal = mrOK then
begin
// Collect current instance parameters
for I := 1 to ParamCount do
begin
S := ParamStr(I);
// Unique log file name for the elevated instance
if CompareText(Copy(S, 1, 5), '/LOG=') = 0 then
begin
S := S + '-localized';
end;
// Do not pass our /SL5 switch
if CompareText(Copy(S, 1, 5), '/SL5=') <> 0 then
begin
Params := Params + AddQuotes(S) + ' ';
end;
end;
L := Languages.Strings[LangCombo.ItemIndex];
P := Pos('=', L);
L := Copy(L, 0, P-1);
// ... and add selected language
Params := Params + '/LANG=' + L;
Instance := ShellExecute(0, '', ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
if Instance <= 32 then
begin
MsgBox(
Format('Running installer with selected language failed. Code: %d', [Instance]),
mbError, MB_OK);
end;
end;
end;
function InitializeSetup(): Boolean;
var
Language: string;
begin
Result := True;
Language := ExpandConstant('{param:LANG}');
if Language = '' then
begin
Log('No language specified, showing language dialog');
SelectLanguage();
Result := False;
Exit;
end
else
begin
Log('Language specified, proceeding with installation');
end;
end;
procedure RedesignWizardForm;
begin
with WizardForm do
begin
BorderIcons:=[];
Bevel1.Hide;
AutoScroll := False;
ClientHeight := ScaleY(349);
end;
with WizardForm.CancelButton do
begin
Top := ScaleY(319);
end;
with WizardForm.NextButton do
begin
Top := ScaleY(319);
end;
with WizardForm.BackButton do
begin
Top := ScaleY(319);
end;
with WizardForm.WizardBitmapImage do
begin
Width := ScaleX(500);
end;
with WizardForm.WelcomeLabel2 do
begin
Visible := False;
end;
with WizardForm.WelcomeLabel1 do
begin
Visible := False;
end;
with WizardForm.WizardSmallBitmapImage do
begin
Left := ScaleX(0);
Width := ScaleX(500);
Height := ScaleY(60);
end;
with WizardForm.PageDescriptionLabel do
begin
Visible := False;
end;
with WizardForm.PageNameLabel do
begin
Visible := False;
end;
with WizardForm.WizardBitmapImage2 do
begin
Width := ScaleX(500);
ExtractTemporaryFile('WizardForm.WizardBitmapImage2.bmp');
Bitmap.LoadFromFile(ExpandConstant('{tmp}\WizardForm.WizardBitmapImage2.bmp'));
end;
with WizardForm.FinishedLabel do
begin
Visible := False;
end;
with WizardForm.FinishedHeadingLabel do
begin
Visible := False;
end;
end;
procedure InitializeWizard1();
begin
RedesignWizardForm;
WizardForm.DiskSpaceLabel.Visible := False;
end;
procedure InitializeWizard2();
begin
ExtractTemporaryFile('BASS.dll');
ExtractTemporaryFile('CallbackCtrl.dll');
ExtractTemporaryFile('botva2.dll');
ExtractTemporaryFile('MusicButton.png');
ExtractTemporaryFile('Music.mp3');
BASS_Init('{tmp}\Music.mp3')
BASS_CreateOnOffButton(WizardForm, '{tmp}\MusicButton.png', 20, 320, 36, 36, 4)
end;
procedure InitializeWizard();
begin
InitializeWizard1();
InitializeWizard2();
end;
procedure DeinitializeSetup();
begin
BASS_DeInit; //Îñâîáîæäàåì ïðîöåññ
gdipShutdown
end;
This code includes the language selector Inno Setup - How to change a label caption [or other controls in general], when selected value in combox box changes and a code that define music and music button. If i delete all about language selector, the code works fine. What is the problem?
The code of music and button includes: botva2.iss, BASS_Module.iss, botva2.dll, CallbackCtrl.dll.
This error appears when you select accept or cancel on the language selector.
The DeinitializeSetup is called, even when the setup is aborted by returning False from the InitializeSetup.
So I guess the BASS_DeInit (or the gdipShutdown) fails, because an equivalent BASS_Init was never called.
You have to avoid calling the code in the DeinitializeSetup, when the BASS_Init was never called.
var
BASS_Initialized: Boolean;
procedure InitializeWizard2();
begin
ExtractTemporaryFile('BASS.dll');
ExtractTemporaryFile('CallbackCtrl.dll');
ExtractTemporaryFile('botva2.dll');
ExtractTemporaryFile('MusicButton.png');
ExtractTemporaryFile('Music.mp3');
BASS_Init('{tmp}\Music.mp3')
BASS_CreateOnOffButton(WizardForm, '{tmp}\MusicButton.png', 20, 320, 36, 36, 4);
BASS_Initialized := True;
end;
procedure DeinitializeSetup();
begin
if BASS_Initialized then
begin
BASS_DeInit;
gdipShutdown;
end;
end;

Add image into the components list - component description

I would like to display a little image, when user hovers mouse cursor over a component on the "Select Components" page.
For example, I would like to do something like this:
I found a half solution here: Long descriptions on Inno Setup components.
But I'm missing the image part.
Building upon my answer to Long descriptions on Inno Setup components. You will need to copy HoverTimerProc and its supporting functions and global variables.
This answer modifies the HoverComponentChanged and InitializeWizard procedures to support the images in addition to description labels.
[Files]
...
Source: Main.bmp; Flags: dontcopy
Source: Additional.bmp; Flags: dontcopy
Source: Help.bmp; Flags: dontcopy
[Code]
var
CompLabel: TLabel;
CompImage: TBitmapImage;
LoadingImage: Boolean;
procedure HoverComponentChanged(Index: Integer);
var
Description: string;
Image: string;
ImagePath: string;
begin
case Index of
0: begin Description := 'This is the description of Main Files'; Image := 'main.bmp'; end;
1: begin Description := 'This is the description of Additional Files'; Image := 'additional.bmp'; end;
2: begin Description := 'This is the description of Help Files'; Image := 'help.bmp'; end;
else
Description := 'Move your mouse over a component to see its description.';
end;
CompLabel.Caption := Description;
if Image <> '' then
begin
{ The ExtractTemporaryFile pumps the message queue, prevent recursion }
if not LoadingImage then
begin
LoadingImage := True;
try
ImagePath := ExpandConstant('{tmp}\' + Image);
if not FileExists(ImagePath) then
begin
ExtractTemporaryFile(Image);
end;
CompImage.Bitmap.LoadFromFile(ImagePath);
finally
LoadingImage := False;
end;
end;
CompImage.Visible := True;
end
else
begin
CompImage.Visible := False;
end;
end;
procedure InitializeWizard();
var
HoverTimerCallback: LongWord;
begin
{ For HoverTimerProc and its supporting functions, }
{ see https://stackoverflow.com/q/10867087/850848#37796528 }
HoverTimerCallback := WrapTimerProc(#HoverTimerProc, 4);
SetTimer(0, 0, 50, HoverTimerCallback);
CompLabel := TLabel.Create(WizardForm);
CompLabel.Parent := WizardForm.SelectComponentsPage;
CompLabel.Left := WizardForm.ComponentsList.Left;
CompLabel.Width := (WizardForm.ComponentsList.Width - ScaleX(16)) div 2;
CompLabel.Height := ScaleY(64);
CompLabel.Top := WizardForm.ComponentsList.Top + WizardForm.ComponentsList.Height - CompLabel.Height;
CompLabel.AutoSize := False;
CompLabel.WordWrap := True;
CompImage := TBitmapImage.Create(WizardForm);
CompImage.Parent := WizardForm.SelectComponentsPage;
CompImage.Top := CompLabel.Top;
CompImage.Width := CompImage.Width;
CompImage.Height := CompLabel.Height;
CompImage.Left := WizardForm.ComponentsList.Left + WizardForm.ComponentsList.Width - CompLabel.Width;
WizardForm.ComponentsList.Height := WizardForm.ComponentsList.Height - CompLabel.Height - ScaleY(8);
end;

How to get the current process list in windows

I'm trying to show all processes which are currently running on my system.
Can any one guide me to do that.
I would use WMI for this task since WMI can list also 64-bit processes which I think the Windows API way cannot. Here is an example which uses the Win32_Process class to query list of running processes:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
const
WM_SETREDRAW = $000B;
var
ProcessList: TNewListBox;
// this is a substitution for missing BeginUpdate method
// of TStrings class
procedure WinControlBeginUpdate(Control: TWinControl);
begin
SendMessage(Control.Handle, WM_SETREDRAW, 0, 0);
end;
// this is a substitution for missing EndUpdate method
// of TStrings class
procedure WinControlEndUpdate(Control: TWinControl);
begin
SendMessage(Control.Handle, WM_SETREDRAW, 1, 0);
Control.Refresh;
end;
function GetProcessNames(Items: TStrings): Integer;
var
I: Integer;
WQLQuery: string;
WbemLocator: Variant;
WbemServices: Variant;
WbemObject: Variant;
WbemObjectSet: Variant;
begin
Result := 0;
WbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
WbemServices := WbemLocator.ConnectServer('localhost', 'root\CIMV2');
WQLQuery := 'SELECT Name FROM Win32_Process';
WbemObjectSet := WbemServices.ExecQuery(WQLQuery);
if not VarIsNull(WbemObjectSet) and (WbemObjectSet.Count > 0) then
begin
Items.Clear;
for I := 0 to WbemObjectSet.Count - 1 do
begin
WbemObject := WbemObjectSet.ItemIndex(I);
if not VarIsNull(WbemObject) then
Items.Add(WbemObject.Name);
end;
Result := Items.Count;
end;
end;
procedure RefreshButtonClick(Sender: TObject);
begin
// this try..finally block is used to reduce annoying visual effects
// when filling the list box; Inno Setup doesn't publish BeginUpdate,
// EndUpdate method pair, hence this home brewn solution
WinControlBeginUpdate(ProcessList);
try
GetProcessNames(ProcessList.Items);
finally
WinControlEndUpdate(ProcessList);
end;
end;
procedure InitializeWizard;
var
RefreshBtn: TNewButton;
ProcessPage: TWizardPage;
begin
ProcessPage := CreateCustomPage(wpWelcome, 'Caption', 'Description');
ProcessList := TNewListBox.Create(WizardForm);
ProcessList.Parent := ProcessPage.Surface;
ProcessList.SetBounds(0, 0, ProcessPage.SurfaceWidth,
ProcessPage.SurfaceHeight - 33);
RefreshBtn := TNewButton.Create(WizardForm);
RefreshBtn.Parent := ProcessPage.Surface;
RefreshBtn.Left := 0;
RefreshBtn.Top := ProcessPage.SurfaceHeight - RefreshBtn.Height;
RefreshBtn.Caption := 'Refresh';
RefreshBtn.OnClick := #RefreshButtonClick;
end;

Long descriptions on Inno Setup components

I am building an install with Inno Setup and I am using the components section to allow the end user to select optional items to install.
Some of these items need a longer description in order for the user to have enough information to intelligently select them.
Is there a way to add more in-depth descriptions somewhere?
This solution uses only Inno Setup proper (not the obsolete 3rd party build of the Inno Setup of suspicious origin).
The solution is partially based on my answer to Inno Setup: OnHover event.
Adjust the HoverComponentChanged procedure to your needs.
[Code]
var
LastMouse: TPoint;
CompLabel: TLabel;
function GetCursorPos(var lpPoint: TPoint): BOOL;
external 'GetCursorPos#user32.dll stdcall';
function SetTimer(
hWnd: longword; nIDEvent, uElapse: LongWord; lpTimerFunc: LongWord): LongWord;
external 'SetTimer#user32.dll stdcall';
function ScreenToClient(hWnd: HWND; var lpPoint: TPoint): BOOL;
external 'ScreenToClient#user32.dll stdcall';
function ClientToScreen(hWnd: HWND; var lpPoint: TPoint): BOOL;
external 'ClientToScreen#user32.dll stdcall';
function ListBox_GetItemRect(
const hWnd: HWND; const Msg: Integer; Index: LongInt; var Rect: TRect): LongInt;
external 'SendMessageW#user32.dll stdcall';
const
LB_GETITEMRECT = $0198;
LB_GETTOPINDEX = $018E;
function FindControl(Parent: TWinControl; P: TPoint): TControl;
var
Control: TControl;
WinControl: TWinControl;
I: Integer;
P2: TPoint;
begin
for I := 0 to Parent.ControlCount - 1 do
begin
Control := Parent.Controls[I];
if Control.Visible and
(Control.Left <= P.X) and (P.X < Control.Left + Control.Width) and
(Control.Top <= P.Y) and (P.Y < Control.Top + Control.Height) then
begin
if Control is TWinControl then
begin
P2 := P;
ClientToScreen(Parent.Handle, P2);
WinControl := TWinControl(Control);
ScreenToClient(WinControl.Handle, P2);
Result := FindControl(WinControl, P2);
if Result <> nil then Exit;
end;
Result := Control;
Exit;
end;
end;
Result := nil;
end;
function PointInRect(const Rect: TRect; const Point: TPoint): Boolean;
begin
Result :=
(Point.X >= Rect.Left) and (Point.X <= Rect.Right) and
(Point.Y >= Rect.Top) and (Point.Y <= Rect.Bottom);
end;
function ListBoxItemAtPos(ListBox: TCustomListBox; Pos: TPoint): Integer;
var
Count: Integer;
ItemRect: TRect;
begin
Result := SendMessage(ListBox.Handle, LB_GETTOPINDEX, 0, 0);
Count := ListBox.Items.Count;
while Result < Count do
begin
ListBox_GetItemRect(ListBox.Handle, LB_GETITEMRECT, Result, ItemRect);
if PointInRect(ItemRect, Pos) then Exit;
Inc(Result);
end;
Result := -1;
end;
procedure HoverComponentChanged(Index: Integer);
var
Description: string;
begin
case Index of
0: Description := 'This is the description of Main Files';
1: Description := 'This is the description of Additional Files';
2: Description := 'This is the description of Help Files';
else
Description := 'Move your mouse over a component to see its description.';
end;
CompLabel.Caption := Description;
end;
procedure HoverTimerProc(
H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
var
P: TPoint;
Control: TControl;
Index: Integer;
begin
GetCursorPos(P);
if P <> LastMouse then { just optimization }
begin
LastMouse := P;
ScreenToClient(WizardForm.Handle, P);
if (P.X < 0) or (P.Y < 0) or
(P.X > WizardForm.ClientWidth) or (P.Y > WizardForm.ClientHeight) then
begin
Control := nil;
end
else
begin
Control := FindControl(WizardForm, P);
end;
Index := -1;
if (Control = WizardForm.ComponentsList) and
(not WizardForm.TypesCombo.DroppedDown) then
begin
P := LastMouse;
ScreenToClient(WizardForm.ComponentsList.Handle, P);
Index := ListBoxItemAtPos(WizardForm.ComponentsList, P);
end;
HoverComponentChanged(Index);
end;
end;
procedure InitializeWizard();
begin
SetTimer(0, 0, 50, CreateCallback(#HoverTimerProc));
CompLabel := TLabel.Create(WizardForm);
CompLabel.Parent := WizardForm.SelectComponentsPage;
CompLabel.Left := WizardForm.ComponentsList.Left;
CompLabel.Width := WizardForm.ComponentsList.Width;
CompLabel.Height := ScaleY(32);
CompLabel.Top :=
WizardForm.ComponentsList.Top + WizardForm.ComponentsList.Height -
CompLabel.Height;
CompLabel.AutoSize := False;
CompLabel.WordWrap := True;
WizardForm.ComponentsList.Height :=
WizardForm.ComponentsList.Height - CompLabel.Height - ScaleY(8);
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 the Unicode version of Inno Setup 5.
To make this work correctly in resized/resizable/modern wizard, you will need some adjustments. See Inno Setup - how to center an animated gif in resized wizard and Long components descriptions in enlarged Inno Setup wizard.
Use this advanced compiler (download link is somewhere below).
It supports more classes and events than the standard compiler. You can access the property "OnItemMouseMove". Using this you can store a description for every item that is shown by a label. Here is an example:
var
CompLabel: TLabel;
procedure OnItemMouseMove(Sender: TObject; X, Y: Integer; Index: Integer; Area: TItemArea);
begin
case Index of
0: CompLabel.Caption := 'This is the description of Component 1';
1: CompLabel.Caption := 'This is the description of Component 2';
2: CompLabel.Caption := 'This is the description of Component 3';
3: CompLabel.Caption := 'This is the description of Component 4'
else
CompLabel.Caption := 'Move your mouse over a component to see its description.';
end;
end;
procedure OnMouseLeave(Sender: TObject);
begin
CompLabel.Caption := 'Move your mouse over a component to see its description.';
end;
procedure InitializeWizard();
begin
CompLabel := TLabel.Create(WizardForm);
CompLabel.Parent := WizardForm.SelectComponentsPage;
CompLabel.SetBounds(WizardForm.ComponentsList.Left,180,WizardForm.ComponentsList.Width,200);
CompLabel.Caption := 'Move your mouse over a component to see its description.';
WizardForm.ComponentsList.OnItemMouseMove := #OnItemMouseMove;
WizardForm.ComponentsList.OnMouseLeave := #OnMouseLeave;
WizardForm.ComponentsList.Height := WizardForm.ComponentsList.Height - 40;
end;

Add a custom input field to Inno Setup

I am making use of Inno Setup (it's amazing!). I was hoping to customise the installer so that I can accept a string from the user in the form of an input field and maybe add a message to it.
How can I do this? I had a look through the docs, google search and not much came up!
Thanks all for any help
You can use Pascal scripting in InnoSetup to create new pages for the installer. These pages can be integrated into the normal installation flow. This is well documented within the InnoSetup documentation (Google search should also come up with samples). Also the Samples folder within your Program Files\InnoSetup has some code examples.
Some time ago, there was a software called InnoSetup Form designer, which allowed you to visually design the page. The link is still there, but on the page I could not find the download. Maybe if you look around a bit you can find it?
EDIT
This is a sample for a page I made once. This is the code section of the ISS file.[Code]
var
EnableFolderPage: Boolean;
lblBlobFileFolder: TLabel;
lblBlobFileWarning1: TLabel;
lblBlobFileWarning2: TLabel;
tbBlobFileFolder: TEdit;
btnBlobFileFolder: TButton;
function GetBlobFolder(param: String): String;
begin
Result := Trim(tbBlobFileFolder.Text);
end;
{ BlobFileForm_Activate }
procedure BlobFileForm_Activate(Page: TWizardPage);
var
s: string;
begin
s := Trim(tbBlobFileFolder.Text);
if (s = '') then
begin
tbBlobFileFolder.Text := ExpandConstant('{sys}');
end;
end;
{ BlobFileForm_NextButtonClick }
function BlobFileForm_NextButtonClick(Page: TWizardPage): Boolean;
var
s: string;
begin
s := Trim(tbBlobFileFolder.Text);
if (s = '') then
begin
MsgBox(ExpandConstant('{cm:BlobFileForm_NoFolder}'), mbError, MB_OK);
Result := false;
end else
begin
if not DirExists(s) then
begin
MsgBox(ExpandConstant('{cm:BlobFileForm_DirDoesntExist}'), mbError, MB_OK);
Result := false;
end else
begin
Result := True;
end;
end;
end;
procedure btnBlobFileFolder_Click(sender: TObject);
var
directory: string;
begin
if BrowseForFolder('', directory, true) then
begin
tbBlobFileFolder.Text := directory;
end;
end;
{ BlobFileForm_CreatePage }
function BlobFileForm_CreatePage(PreviousPageId: Integer): Integer;
var
Page: TWizardPage;
begin
Page := CreateCustomPage(
PreviousPageId,
ExpandConstant('{cm:BlobFileForm_Caption}'),
ExpandConstant('{cm:BlobFileForm_Description}')
);
{ lblBlobFileFolder }
lblBlobFileFolder := TLabel.Create(Page);
with lblBlobFileFolder do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_lblBlobFileFolder_Caption0}');
Left := ScaleX(8);
Top := ScaleY(8);
Width := ScaleX(167);
Height := ScaleY(13);
end;
{ lblBlobFileWarning1 }
lblBlobFileWarning1 := TLabel.Create(Page);
with lblBlobFileWarning1 do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning1_Caption0}');
Left := ScaleX(8);
Top := ScaleY(80);
Width := ScaleX(50);
Height := ScaleY(13);
Font.Color := -16777208;
Font.Height := ScaleY(-11);
Font.Name := 'Tahoma';
Font.Style := [fsBold];
end;
{ lblBlobFileWarning2 }
lblBlobFileWarning2 := TLabel.Create(Page);
with lblBlobFileWarning2 do
begin
Parent := Page.Surface;
Caption :=
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption0}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption1}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption2}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption3}') + #13 +
ExpandConstant('{cm:BlobFileForm_lblBlobFileWarning2_Caption4}');
Left := ScaleX(8);
Top := ScaleY(96);
Width := ScaleX(399);
Height := ScaleY(133);
AutoSize := False;
WordWrap := True;
end;
{ tbBlobFileFolder }
tbBlobFileFolder := TEdit.Create(Page);
with tbBlobFileFolder do
begin
Parent := Page.Surface;
Left := ScaleX(8);
Top := ScaleY(24);
Width := ScaleX(401);
Height := ScaleY(21);
TabOrder := 0;
end;
{ btnBlobFileFolder }
btnBlobFileFolder := TButton.Create(Page);
with btnBlobFileFolder do
begin
Parent := Page.Surface;
Caption := ExpandConstant('{cm:BlobFileForm_btnBlobFileFolder_Caption0}');
Left := ScaleX(320);
Top := ScaleY(48);
Width := ScaleX(91);
Height := ScaleY(23);
TabOrder := 1;
end;
with Page do
begin
OnActivate := #BlobFileForm_Activate;
OnNextButtonClick := #BlobFileForm_NextButtonClick;
end;
with btnBlobFileFolder do
begin
OnClick := #btnBlobFileFolder_Click;
end;
Result := Page.ID;
end;
procedure InitializeWizard();
begin
BlobFileForm_CreatePage(wpSelectDir);
end;
EDIT 2
To write the value the user entered to a registry key, create a new function:
function GetUserEnteredText(param: String): String;
begin
Result := Trim(tbTextBox.Text);
end;
This function simply returns what was entered in the text box. Please note that the function must take a string parameter - even though you ignore it!
In the [Registry] section of your script, declare the key that should be written like that:
Root: HKLM; Subkey: SOFTWARE\MyCompany\MyTool; ValueType: string; ValueName: MyValue; ValueData: {code:GetUserEnteredText}; Flags: createvalueifdoesntexist uninsdeletekeyifempty uninsdeletevalue
This creates a registry value named "MyValue" in HKLM\SOFTWARE\MyCompany\MyTool that contains what the user entered in the text box.
Here is shorter code to add a custom page to Inno Setup installer with an Input Field:
var
CustomQueryPage: TInputQueryWizardPage;
procedure AddCustomQueryPage();
begin
CustomQueryPage := CreateInputQueryPage(
wpWelcome,
'Custom message',
'Custom description',
'Custom instructions');
{ Add items (False means it's not a password edit) }
CustomQueryPage.Add('Custom Field:', False);
end;
procedure InitializeWizard();
begin
AddCustomQueryPage();
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
{ Read custom value }
MsgBox('Custom Value = ' + CustomQueryPage.Values[0], mbInformation, MB_OK);
end;
end;

Resources