How to access the ItemIndex property of the TNewComboBox class? - inno-setup

Goal
I'm helping out a friend make an installer using InnoSetup in Conjunction with VCL-Styles plugin. I currently trying to access an index which is supposedly linked to each choice in a ComboBox and for that I use the TNewComboBox.ItemIndex
Error
For some reason, accessing / returning the property doesn't work for us and we recieve this error:
Code
Here is the code
Source: "C:\Program Files (x86)\The Road To Delphi\VCL Styles Inno\VclStylesinno.dll"; DestDir: "{app}";
var
cb: TNewComboBox;
procedure DropDownChange(Sender : TObject );
begin
case cb.ItemIndex of <------- Error line 76 "Could not Call proc"
0:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('0', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
1:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('1', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
2:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('2', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
3:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('3', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
4:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('4', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
5:
begin
Delete(cfgStr, Pos(cvar, cfgStr) + Length(cvar), 1);
Insert('0', cfgStr, Pos(cvar, cfgStr) + Length(cvar));
end;
end
end;
procedure CreateDropDown(page: TWizardPage; caption: String; cb: TNewComboBox; cvar: String; index: Integer; item0, item1, item2, item3, item4, item5 : String);
var
newLabel: TLabel;
begin
newLabel:= TLabel.Create(page);
newLabel.Parent:= page.Surface;
newLabel.Caption:= caption;
case page of
SettingsPageA:
begin
newLabel.Left:= curWidthA;
newLabel.Top:= curHeightA;
case curWidthA of
0:
begin
curWidthA:= curWidthA + 264;
end;
264:
begin
curWidthA:= curWidthA + 264;
end;
528:
begin
curWidthA:= 0;
curHeightA:= curHeightA + 66;
end;
end;
end;
SettingsPageB:
begin
newLabel.Left:= curWidthB;
newLabel.Top:= curHeightB;
case curWidthB of
0:
begin
curWidthB:= curWidthB + 264;
end;
264:
begin
curWidthB:= curWidthB + 264;
end;
528:
begin
curWidthB:= 0;
curHeightB:= curHeightB + 66;
end;
end;
end;
SettingsPageC:
begin
newLabel.Left:= curWidthC;
newLabel.Top:= curHeightC;
case curWidthC of
0:
begin
curWidthC:= curWidthC + 264;
end;
264:
begin
curWidthC:= curWidthC + 264;
end;
528:
begin
curWidthC:= 0;
curHeightC:= curHeightC + 66;
end;
end;
end;
end;
newLabel.Width:= 247;
newLabel.Height:= 25;
newLabel.Transparent:= True;
cb:= TNewComboBox.Create(page);
cb.Parent:= page.Surface;
cb.Left:= newLabel.Left;
cb.Top:= newLabel.Top + newLabel.Height + 4;
cb.Width:= newLabel.Width;
cb.Height:= newLabel.Height;
cb.Style:= csDropDownList;
cb.Items.Add(item0);
if Length(item1) > 0 then
begin
cb.Items.Add(item1);
if Length(item2) > 0 then
begin
cb.Items.Add(item2);
if Length(item3) > 0 then
begin
cb.Items.Add(item3);
if Length(item4) > 0 then
begin
cb.Items.Add(item4);
if Length(item5) > 0 then
begin
cb.Items.Add(item5);
end;
end;
end;
end;
end;
cb.ItemIndex := index;
cb.OnChange:= #DropDownChange; <---------------
end;
This is just a piece of the code which performs an .OnChange whenever the ComboBox changes of choice / Index. Thanks!
Edit
I've been requested for a minimal example of reproducible code shown below
procedure DropDownChanged(Sender: TObject);
begin
Log(IntToStr(cb.ItemIndex);
end;
procedure CreateDropDown(page: TWizardPage; caption: String; cb: TNewComboBox; cvar: String; index: Integer; item0, item1 : String);
var
newLabel: TLabel;
begin
newLabel:= TLabel.Create(page);
newLabel.Parent:= page.Surface;
newLabel.Caption:= caption;
newLabel.Left:= curWidth;
newLabel.Top:= curHeight;
newLabel.Width:= 247;
newLabel.Height:= 25;
newLabel.Transparent:= True;
cb:= TNewComboBox.Create(page);
cb.Parent:= page.Surface;
cb.Left:= newLabel.Left;
cb.Top:= newLabel.Top + newLabel.Height + 4;
cb.Width:= newLabel.Width;
cb.Height:= newLabel.Height;
cb.Style:= csDropDownList;
cb.Items.Add(item0);
cb.Items.Add(item1);
cb.ItemIndex:= index;
cb.OnChange:= DropDownChanged;
end;
And to execute it:
CreateDropDown(wpReady, 'Caption', 'ComboBox', 0, 'First Item', 'Second Item');
Edit 2
This is the original author of this little project. I've watered down the code necessary to recreate the issue, like this:
[Code]
var
ExamplePage: TWizardPage;
cb: TNewComboBox;
procedure DropDownChanged(Sender: TObject);
begin
Log(IntToStr(cb.ItemIndex));
end;
procedure CreateDropDown(page: TWizardPage; cb: TNewComboBox; index: Integer; item0, item1 : String);
begin
cb:= TNewComboBox.Create(page);
cb.Parent:= page.Surface;
cb.Style:= csDropDownList;
cb.Items.Add(item0);
cb.Items.Add(item1);
cb.ItemIndex:= index;
cb.OnChange:= #DropDownChanged;
end;
procedure CreateExamplePage;
var
ExampleComboBox: TNewComboBox;
begin
ExamplePage:= CreateCustomPage(wpWelcome, 'Example Page', 'This is an example');
CreateDropDown(ExamplePage, ExampleComboBox, 0, 'First Item', 'Second Item');
end;
procedure InitializeWizard;
begin
CreateExamplePage;
end;
This is the minimum required to reproduce the error upon selecting an item in the drop-down. I hope this helps clear out the issue!

You have name conflict between the global cb variable and local cb parameter of CreateDropDown function. You assign the local parameter (what makes little sense), while the global variable is never assigned. Plus, it's not really clear, what is the purpose of ExampleComboBox.
Maybe you wanted something like this?
[Code]
var
ExamplePage: TWizardPage;
ExampleComboBox: TNewComboBox;
procedure DropDownChanged(Sender: TObject);
begin
Log(IntToStr(ExampleComboBox.ItemIndex));
end;
function CreateDropDown(
page: TWizardPage; index: Integer; item0, item1: String): TNewComboBox;
begin
Result := TNewComboBox.Create(page);
Result.Parent:= page.Surface;
Result.Style:= csDropDownList;
Result.Items.Add(item0);
Result.Items.Add(item1);
Result.ItemIndex:= index;
Result.OnChange:= #DropDownChanged;
end;
procedure CreateExamplePage;
begin
ExamplePage :=
CreateCustomPage(wpWelcome, 'Example Page', 'This is an example');
ExampleComboBox :=
CreateDropDown(ExamplePage, 0, 'First Item', 'Second Item');
end;
procedure InitializeWizard;
begin
CreateExamplePage;
end;
The DropDownChanged does not have to rely on the global variable. If you want it to work for other comboboxes, rely on the Sender parameter instead
Log(IntToStr(TNewComboBox(Sender).ItemIndex));

Related

Inno Setup - How to add multiple arc files to decompress?

I am using this code: Inno Setup - How to add cancel button to decompressing page? (answer of Martin Prikryl) to decompress an arc file with Inno Setup.
I want to have the possibility of decompress more than one arc file to install files from components selection (for example). But still show on overall progress bar for all extractions. whole Is this possible?
This is modification of my answer to Inno Setup - How to add cancel button to decompressing page?
Prerequisities are the same, refer to the other answer.
In the ExtractArc, call AddArchive for each archive you want to extract.
[Files]
Source: unarc.dll; Flags: dontcopy
[Code]
const
ArcCancelCode = -10;
function FreeArcExtract(
Callback: LongWord;
Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: PAnsiChar): Integer;
external 'FreeArcExtract#files:unarc.dll cdecl';
const
CP_UTF8 = 65001;
function WideCharToMultiByte(CodePage: UINT; dwFlags: DWORD;
lpWideCharStr: string; cchWideChar: Integer; lpMultiByteStr: AnsiString;
cchMultiByte: Integer; lpDefaultCharFake: Integer;
lpUsedDefaultCharFake: Integer): Integer;
external 'WideCharToMultiByte#kernel32.dll stdcall';
function GetStringAsUtf8(S: string): AnsiString;
var
Len: Integer;
begin
Len := WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, 0, 0, 0);
SetLength(Result, Len);
WideCharToMultiByte(CP_UTF8, 0, S, Length(S), Result, Len, 0, 0);
end;
var
ArcTotalSize: Integer;
ArcTotalExtracted: Integer;
ArcExtracted: Integer;
ArcCancel: Boolean;
ArcProgressPage: TOutputProgressWizardPage;
function FreeArcCallback(
AWhat: PAnsiChar; Int1, Int2: Integer; Str: PAnsiChar): Integer;
var
What: string;
begin
What := AWhat;
if What = 'origsize' then
begin
Log(Format('Adding archive with files with total size %d MB', [Int1]));
ArcTotalSize := ArcTotalSize + Int1;
end
else
if What = 'write' then
begin
if ArcTotalSize > 0 then
begin
ArcProgressPage.SetProgress(ArcTotalExtracted + Int1, ArcTotalSize);
end;
ArcExtracted := Int1;
end
else
begin
// Just to pump message queue more often (particularly for 'read' callbacks),
// to get more smooth progress bar
if (ArcExtracted > 0) and (ArcTotalSize > 0) then
begin
ArcProgressPage.SetProgress(ArcTotalExtracted + ArcExtracted, ArcTotalSize);
end;
end;
if ArcCancel then Result := ArcCancelCode
else Result := 0;
end;
procedure FreeArcCmd(
Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: string);
var
ArcResult: Integer;
begin
ArcCancel := False;
ArcResult :=
FreeArcExtract(
CreateCallback(#FreeArcCallback),
GetStringAsUtf8(Cmd1), GetStringAsUtf8(Cmd2), GetStringAsUtf8(Cmd3),
GetStringAsUtf8(Cmd4), GetStringAsUtf8(Cmd5), GetStringAsUtf8(Cmd6),
GetStringAsUtf8(Cmd7), GetStringAsUtf8(Cmd8), GetStringAsUtf8(Cmd9),
GetStringAsUtf8(Cmd10));
if ArcCancel then
begin
RaiseException('Extraction cancelled');
end
else
if ArcResult <> 0 then
begin
RaiseException(Format('Extraction failed with code %d', [ArcResult]));
end;
end;
var
ArcArchives: array of string;
procedure AddArchive(ArchivePath: string);
begin
SetArrayLength(ArcArchives, GetArrayLength(ArcArchives) + 1);
ArcArchives[GetArrayLength(ArcArchives) - 1] := ArchivePath;
FreeArcCmd('l', '--', ArchivePath, '', '', '', '', '', '', '');
end;
procedure UnPackArchives(DestPath: string);
var
I: Integer;
ArchivePath: string;
begin
Log(Format('Total size of files to be extracted is %d MB', [ArcTotalSize]));
ArcTotalExtracted := 0;
for I := 0 to GetArrayLength(ArcArchives) - 1 do
begin
ArcExtracted := 0;
ArchivePath := ArcArchives[I];
Log(Format('Extracting %s', [ArchivePath]));
FreeArcCmd('x', '-o+', '-dp' + DestPath, '-w' + DestPath, '--', ArchivePath,
'', '', '', '');
ArcTotalExtracted := ArcTotalExtracted + ArcExtracted;
end;
end;
procedure UnpackCancelButtonClick(Sender: TObject);
begin
ArcCancel := True;
end;
procedure ExtractArc;
var
PrevCancelButtonClick: TNotifyEvent;
begin
ArcProgressPage :=
CreateOutputProgressPage('Decompression', 'Decompressing archive...');
ArcProgressPage.SetProgress(0, 100);
ArcProgressPage.Show;
try
WizardForm.CancelButton.Visible := True;
WizardForm.CancelButton.Enabled := True;
PrevCancelButtonClick := WizardForm.CancelButton.OnClick;
WizardForm.CancelButton.OnClick := #UnpackCancelButtonClick;
try
AddArchive(ExpandConstant('{src}\test1.arc'));
AddArchive(ExpandConstant('{src}\test2.arc'));
Log('Arc extraction starting');
UnPackArchives(ExpandConstant('{app}'));
except
MsgBox(GetExceptionMessage(), mbError, MB_OK);
end;
finally
Log('Arc extraction done');
ArcProgressPage.Hide;
WizardForm.CancelButton.OnClick := PrevCancelButtonClick;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
ExtractArc;
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.

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;

Inno Setup: Enlarge component page only with preview and description

I need help from you to merge two scripts found here to have preview image on a maximized components windows:
Add image into the components list - component description
Long descriptions on Inno Setup components
Edit: Thanks to Martin Prikryl. With his patience and his help, I successfully merge these two scripts.
Your preview image should have a resolution of 208x165 in .bmp
[Files]
...
Source: "1.bmp"; Flags: dontcopy
Source: "2.bmp"; Flags: dontcopy
Source: "3.bmp"; Flags: dontcopy
Source: "InnoCallback.dll"; Flags: dontcopy
[Code]
var
LastMouse: TPoint;
type
TTimerProc = procedure(H: LongWord; Msg: LongWord; IdEvent: LongWord; Time: LongWord);
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 WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord; external 'wrapcallback#files:InnoCallback.dll stdcall';
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;
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 := 'Component 1'; Image := '1.bmp'; end;
1: begin Description := 'Component 2'; Image := '2.bmp'; end;
2: begin Description := 'Component 3'; Image := '3.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 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 then
begin
P := LastMouse;
ScreenToClient(WizardForm.ComponentsList.Handle, P);
Index := ListBoxItemAtPos(WizardForm.ComponentsList, P);
end;
HoverComponentChanged(Index);
end;
end;
type
TPositionStorage = array of Integer;
var
CompPageModified: Boolean;
CompPagePositions: TPositionStorage;
procedure SaveComponentsPage(out Storage: TPositionStorage);
begin
SetArrayLength(Storage, 10);
Storage[0] := WizardForm.Height;
Storage[1] := WizardForm.NextButton.Top;
Storage[2] := WizardForm.BackButton.Top;
Storage[3] := WizardForm.CancelButton.Top;
Storage[4] := WizardForm.ComponentsList.Height;
Storage[5] := WizardForm.OuterNotebook.Height;
Storage[6] := WizardForm.InnerNotebook.Height;
Storage[7] := WizardForm.Bevel.Top;
Storage[8] := WizardForm.BeveledLabel.Top;
Storage[9] := WizardForm.ComponentsDiskSpaceLabel.Top;
end;
procedure LoadComponentsPage(const Storage: TPositionStorage;
HeightOffset: Integer);
begin
if GetArrayLength(Storage) <> 10 then
RaiseException('Invalid storage array length.');
WizardForm.Height := Storage[0] + HeightOffset;
WizardForm.NextButton.Top := Storage[1] + HeightOffset;
WizardForm.BackButton.Top := Storage[2] + HeightOffset;
WizardForm.CancelButton.Top := Storage[3] + HeightOffset;
WizardForm.ComponentsList.Height := Storage[4] + ScaleY(150);
WizardForm.OuterNotebook.Height := Storage[5] + HeightOffset;
WizardForm.InnerNotebook.Height := Storage[6] + HeightOffset;
WizardForm.Bevel.Top := Storage[7] + HeightOffset;
WizardForm.BeveledLabel.Top := Storage[8] + HeightOffset;
WizardForm.ComponentsDiskSpaceLabel.Top := Storage[9] + HeightOffset;
end;
procedure InitializeWizard1();
var
HoverTimerCallback: LongWord;
begin
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 div 2;
CompLabel.Height := ScaleY(64);
CompLabel.Top := WizardForm.ComponentsList.Top + WizardForm.ComponentsList.Height - CompLabel.Height + ScaleY(150);
CompLabel.AutoSize := false;
CompLabel.WordWrap := True;
CompImage := TBitmapImage.Create(WizardForm);
CompImage.Parent := WizardForm.SelectComponentsPage;
CompImage.Top := CompLabel.Top;
CompImage.Width := CompImage.Width + ScaleX(128);
CompImage.Height := CompLabel.Height + ScaleX(128);
CompImage.Left := WizardForm.ComponentsList.Left + WizardForm.ComponentsList.Width - CompLabel.Width;
WizardForm.ComponentsList.Height := WizardForm.ComponentsList.Height - CompLabel.Height - ScaleY(8);
end;
procedure InitializeWizard2();
begin
CompPageModified := False;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurpageID = wpSelectComponents then
begin
SaveComponentsPage(CompPagePositions);
LoadComponentsPage(CompPagePositions, ScaleY(250));
CompPageModified := True;
end
else
if CompPageModified then
begin
LoadComponentsPage(CompPagePositions, 0);
CompPageModified := False;
end;
end;
procedure InitializeWizard();
begin
InitializeWizard1();
InitializeWizard2();
end;
Just copy both codes and merge the InitializeWizard implementations according to this guide:
Merging event function (InitializeWizard) implementations from different sources.
Though, actually, the InitializeWizard in the Larger “Select Components” page in Inno Setup is noop. You can skip that. And you do not need to merge anything.
The only other change you need to do, is not to enlarge the ComponentsList in the LoadComponentsPage.

Merging event function (InitializeWizard) implementations from different sources

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

how to pass a variable value to an xml file?

this is my code to enter a port number from user.upon installing i want to get the port number changed in apache tomcat server.xml file.
Iam passing apache tomcat zip file also using files section and unzip it in run section
var
javaVersion: String;
javaPath: String;
//port number code
function SetFocus(hWnd: HWND): HWND;
external 'SetFocus#user32.dll stdcall';
var
SerialPage: TWizardPage;
SerialEdits: array of TEdit;
const
CF_TEXT = 1;
VK_BACK = 8;
SC_EDITCOUNT = 1;
SC_CHARCOUNT = 4;
procedure OnSerialEditChange(Sender: TObject);
var
I: Integer;
CanContinue: Boolean;
begin
CanContinue := True;
for I := 0 to GetArrayLength(SerialEdits) - 1 do
if Length(SerialEdits[I].Text) < SC_CHARCOUNT then
begin
CanContinue := False;
Break;
end;
WizardForm.NextButton.Enabled := CanContinue;
end;
function GetSerialNumber(Param: String): string;
var
I: Integer;
begin
Result := '';
for I := 0 to GetArrayLength(SerialEdits) - 1 do
Result := Result + SerialEdits[I].Text ;
end;
procedure OnSerialEditKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
var
Edit: TEdit;
EditIndex: Integer;
begin
Edit := TEdit(Sender);
EditIndex := Edit.TabOrder - SerialEdits[0].TabOrder;
if (EditIndex = 0) and (Key = Ord('V')) and (Shift = [ssCtrl]) then
begin
if TryPasteSerialNumber then
Key := 0;
end
else
if (Key >= 32) and (Key <= 255) then
begin
if Length(Edit.Text) = SC_CHARCOUNT - 1 then
begin
if EditIndex < GetArrayLength(SerialEdits) - 1 then
SetFocus(SerialEdits[EditIndex + 1].Handle)
else
SetFocus(WizardForm.NextButton.Handle);
end;
end
else
if Key = VK_BACK then
if (EditIndex > 0) and (Edit.Text = '') and (Edit.SelStart = 0) then
SetFocus(SerialEdits[EditIndex - 1].Handle);
end;
procedure CreateSerialNumberPage;
var
I: Integer;
Edit: TEdit;
DescLabel: TLabel;
EditWidth: Integer;
begin
SerialPage := CreateCustomPage(wpWelcome, 'Serial number validation',
'Enter the valid serial number');
DescLabel := TLabel.Create(SerialPage);
DescLabel.Top := 16;
DescLabel.Left := 0;
DescLabel.Parent := SerialPage.Surface;
DescLabel.Caption := 'Enter the valid serial number and continue with the installation...';
DescLabel.Font.Style := [fsBold];
SetArrayLength(SerialEdits, SC_EDITCOUNT);
EditWidth := (SerialPage.SurfaceWidth - ((SC_EDITCOUNT - 1) * 8)) div SC_EDITCOUNT;
for I := 0 to SC_EDITCOUNT - 1 do
begin
Edit := TEdit.Create(SerialPage);
Edit.Top := 40;
Edit.Left := I * (EditWidth + 8);
Edit.Width := EditWidth;
Edit.CharCase := ecUpperCase;
Edit.MaxLength := SC_CHARCOUNT;
Edit.Parent := SerialPage.Surface;
Edit.OnChange := #OnSerialEditChange;
Edit.OnKeyDown := #OnSerialEditKeyDown;
SerialEdits[I] := Edit;
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = SerialPage.ID then
WizardForm.NextButton.Enabled := False;
end;
procedure InitializeWizard;
begin
CreateSerialNumberPage;
end ;
i want to replace the port number which was entered by user in tomcats server.xml using tokens
<Connector port="##portnumber##" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
Here's a script I've made for you. I've changed the way of entering port number and shown how to modify attribute values in XML files. Also notice the usage of the AfterInstall function:
#define TomcatDest "{app}\tomcat"
#define TomcatFullPath TomcatDest + "\apache-tomcat-7.0.42"
#define TomcatSrvConfigFile TomcatFullPath + "\conf\server.xml"
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Files]
Source: "unzip.exe"; DestDir: "{tmp}\installertemps"
Source: "apache-tomcat-7.0.42.zip"; DestDir: "{tmp}\installertemps"
[Run]
Filename: "{tmp}\installertemps\unzip.exe"; Parameters: " ""{tmp}\installertemps\apache-tomcat-7.0.42.zip"" -d ""{#TomcatDest}"" "; AfterInstall: UpdateConfigFile(ExpandConstant('{#TomcatSrvConfigFile}'))
[Code]
const
DefaultPort = 8080;
var
ConfigPage: TInputQueryWizardPage;
procedure SaveAttrValueToXML(const FileName, NodePath, Attribute,
Value: string);
var
XMLNode: Variant;
XMLDocument: Variant;
begin
XMLDocument := CreateOleObject('Msxml2.DOMDocument');
try
XMLDocument.async := False;
XMLDocument.load(FileName);
if (XMLDocument.parseError.errorCode <> 0) then
MsgBox('The XML file could not be parsed. ' +
XMLDocument.parseError.reason, mbError, MB_OK)
else
begin
XMLDocument.setProperty('SelectionLanguage', 'XPath');
XMLNode := XMLDocument.selectSingleNode(NodePath);
XMLNode.setAttribute(Attribute, Value);
XMLDocument.save(FileName);
end;
except
MsgBox('An error occured!' + #13#10 + GetExceptionMessage,
mbError, MB_OK);
end;
end;
procedure InitializeWizard;
begin
ConfigPage := CreateInputQueryPage(wpSelectDir, 'Tomcat configuration',
'Description', 'SubCaption');
ConfigPage.Add('Port:', False);
ConfigPage.Values[0] := IntToStr(DefaultPort);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
PortNumber: Integer;
begin
Result := True;
if CurPageID = ConfigPage.ID then
begin
PortNumber := StrToIntDef(ConfigPage.Values[0], -1);
// modify the statement to allow users enter only valid port numbers;
// currently the value of -1 means that there is not even a number entered
// in the edit box
if (PortNumber = -1) then
begin
Result := False;
MsgBox('You''ve entered invalid port number. The setup cannot continue...', mbError, MB_OK);
end;
end;
end;
procedure UpdateConfigFile(const FileName: string);
begin
SaveAttrValueToXML(FileName, '//Server/Service/Connector', 'port',
ConfigPage.Values[0]);
end;

Resources