Some time back I had this question. Some of that code is repeated here:
procedure RebuildRunList;
var
RunEntries: array of TRunEntry;
I: Integer;
begin
{ Save run list ... }
SetArrayLength(RunEntries, WizardForm.RunList.Items.Count);
for I := 0 to WizardForm.RunList.Items.Count - 1 do
begin
RunEntries[I].Caption := WizardForm.RunList.ItemCaption[I];
RunEntries[I].Checked := WizardForm.RunList.Checked[I];
RunEntries[I].Object := WizardForm.RunList.ItemObject[I];
end;
{ ... clear it ... }
WizardForm.RunList.Items.Clear;
{ ... and re-create }
for I := 0 to GetArrayLength(RunEntries) - 1 do
begin
{ the first two entries are radio buttons }
if (I = 0) or (I = 1) then
begin
WizardForm.RunList.AddRadioButton(
RunEntries[I].Caption, '', 0, RunEntries[I].Checked, True, RunEntries[I].Object);
end
else
begin
WizardForm.RunList.AddCheckBox(
RunEntries[I].Caption, '', 0, RunEntries[I].Checked, True, True, True,
RunEntries[I].Object);
end;
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then
begin
{ Only now is the RunList populated. }
{ Two entries are on 64-bit systems only. }
if IsWin64 then RebuildRunList;
end;
end;
I would like to know how I can make a enhancement to the radio choices. The drawback at the moment is that the user is forced to start one or the other application. I would like to add another radio option for simply closing down the installer. Ideally it would use a Inno Setup supplied message so that I do not have to ask for translations. (See this question).
Can this be done?
The easiest solution is to add a no-op entry to the RunList:
[Run]
...
Filename: "{cmd}"; Parameters: "/C exit"; Description: "Exit setup"; \
Flags: nowait postinstall runasoriginaluser unchecked skipifsilent runhidden; \
Check: IsWin64
And turn it to a radio button:
{ the first three entries are radio buttons }
if (I = 0) or (I = 1) or (I = 2) then
Related
I'm trying to write an installer in Inno Setup that includes a page where a TNewCheckListBox is created and then used to populate a selection of choices on the next page:
var
ListBox: TNewCheckListBox;
SelectedConfigs: array of Integer;
function ConfigurationPage(): Integer;
var
Page: TWizardPage;
I: Integer;
begin
Page := CreateCustomPage(wpSelectComponents, 'Select Configuration',
'Please select the configuration you would like to install to.');
ListBox := TNewCheckListBox.Create(Page);
ListBox.Parent := Page.Surface;
ListBox.Left := ScaleX(0);
ListBox.Top := ScaleY(0);
ListBox.Width := Page.SurfaceWidth;
ListBox.Height := Page.SurfaceHeight;
ListBox.BorderStyle := bsNone;
for I := 0 to GetArrayLength(ConfigurationNames) - 1 do
begin
ListBox.AddCheckBox(ConfigurationNames[I], '', 0, False, True, False, False, nil);
end;
ListBox.ItemIndex := 0;
Result := Page.ID;
end;
procedure PortSelectionPage(PageID: Integer);
var
Page: TWizardPage;
ArrayLength: Integer;
I: Integer;
begin
Page := CreateCustomPage(PageID, 'Select Port',
'Please select the port you want the configurations to receive on.');
for I := 0 to ListBox.Items.Count - 1 do
begin
Log(Format('ListBox[%d], %s is checked? %d', [I, ListBox.Items[I], ListBox.Checked[I]]));
if ListBox.Checked[I] then
begin
ArrayLength := GetArrayLength(SelectedConfigs);
SetArrayLength(SelectedConfigs, ArrayLength + 1);
SelectedConfigs[ArrayLength] := I;
Log(Format('Config %d was selected...', [I]));
end;
end;
end;
procedure InitializeWizard;
var
PageID: Integer;
begin
PageID := ConfigurationPage;
PortSelectionPage(PageID);
end;
The problem I'm having is that, regardless of the selections I make in the ConfigurationPage procedure, the SelectedConfigs array is not being updated and my debugging messages are showing that none of the options are selected. Before creating the PortSelectionPage procedure, the code lived on the CurStepChanged event handler so I'm not sure if the issue is with my having moved the code to a different page or if there's something else going on here. Do I need to force an update to the component or should I be using event handlers instead? If so, how would I implement one for this usecase?
Your whole code is executed from InitializeWizard event function. Hence even before the installer wizard is shown.
You have to query the checkbox states only after the user changes them. For example, from some of these events:
NextButtonClick
CurStepChanged
CurPageChanged
I would like to create a custom Inno Setup page that will be displayed at the very beginning of installation, with 3 radio buttons to select from. That selection should set the value in #define BinVer. Currently I'm forced to make 3 separate installers, but I prefer to have just one with such feature. I'm setting it up like that for each exe:
#define BinVer "111111", in another compilation I change it to #define BinVer "222222", and finaly I have #define BinVer "333333" for the third compilation. I'm using this define in such situations li ke that:
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
begin
case CurUninstallStep of
usPostUninstall:
begin
CreateDir(ExpandConstant('{app}\bin\{#BinVer}\files'));
end;
end;
end;
or
Source: "{#CompPath}\Folder\sed.exe"; DestDir: "{app}\bin\{#BinVer}\files\gui\unbound\"; Flags: deleteafterinstall
So far I have tried something like this, but it's just very beginning, not sure what to do next, I'm not that good with programming it:
[Code]
procedure InitializeWizard();
var
Page: TInputOptionWizardPage;
begin
Page := CreateInputOptionPage(wpWelcome, '', '', '', False, False);
Page.AddEx('Radio button 1', 0, True);
Page.AddEx('Radio button 2', 0, True);
Page.AddEx('Radio button 3', 0, True);
end;
Also tried something like that (but I don't know if this is correct way to achieve this, it's not even compiling without errors):
[Code]
var
BinVer: string;
function InitializeSetup(): Boolean;
var
Page: TInputOptionWizardPage;
begin
Page := CreateInputOptionPage(wpWelcome, 'Select BinVer', 'Select BinVer', '', False, False);
Page.Add('BinVer 1', '111111');
Page.Add('BinVer 2', '222222');
Page.Add('BinVer 3', '333333');
Page.DefaultValueIndex := 0; // initially select the first radio button
Result := True;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
Page: TInputOptionWizardPage;
begin
Result := True;
if CurPageID = wpWelcome then
begin
Page := GetInputOptionPage(wpWelcome);
case Page.SelectedValueIndex of
0: BinVer := '111111';
1: BinVer := '222222';
2: BinVer := '333333';
end;
end;
end;
You cannot change a value of a preprocessor variable (used on compile-time) on runtime.
But you do not have to. You want to use the value on runtime from Pascal Code. And you already have the value stored to runtime Pascal variable (BinVer: string). So use that:
CreateDir(ExpandConstant('{app}\bin\' + BinVer + '\files'));
Though you do not really need to store the value to the global variable either. You can query the "InputOptionPage" when needed.
Example of full code, linking the selection to the [Files] section entry destination:
[Files]
Source: "{#CompPath}\Folder\sed.exe"; \
DestDir: "{app}\bin\{code:GetBinVer}\files\gui\unbound\"; \
Flags: deleteafterinstall
[Code]
var
InputOptionPage: TInputOptionWizardPage;
procedure InitializeWizard();
begin
InputOptionPage := CreateInputOptionPage(wpWelcome, '', '', '', False, False);
InputOptionPage.AddEx('Radio button 1', 0, True);
InputOptionPage.AddEx('Radio button 2', 0, True);
InputOptionPage.AddEx('Radio button 3', 0, True);
// initially select the first radio button
InputOptionPage.SelectedValueIndex := 0;
end;
function GetBinVer(Param: string): string;
begin
case InputOptionPage.SelectedValueIndex of
0: BinVer := '111111';
1: BinVer := '222222';
2: BinVer := '333333';
else RaiseException('Unexpected selection');
end;
end;
(not tested)
Though linking it to a code in CurUninstallStepChanged is more complicated, that happens in another (uninstaller) process. So you will have to store the selection/BinVer somewhere (e.g. Windows Registry), where you will load it from in the uninstaller. You might by able to use SetPreviousData/GetPreviousData.
[Components]
Name: "Slasher"; Description: "Dagon Slasher"; Types: Slasher Full
Name: "Frankenstein"; Description: "Dagon Frankenstein"; Types: Frankenstein Full
[Types]
Name: "Full"; Description: "Dagon Video Tools"
Name: "Slasher"; Description: "Dagon Slasher"
Name: "Frankenstein"; Description: "Dagon FrankenStein"
[Tasks]
Name: "Debug"; Description: "Nothing"; Components: not Slasher
Name: "Vid"; Description: "Install Extra Codecs for Frankenstein"; Flags: unchecked; Components: not Slasher
[Code]
var
Warning: TNewStaticText;
procedure InitializeWizard;
begin
Warning := TNewStaticText.Create(WizardForm);
Warning.Parent := WizardForm.SelectTasksPage;
Warning.Visible := False;
Warning.AutoSize := False;
Warning.SetBounds(
WizardForm.TasksList.Left,
WizardForm.TasksList.Top + WizardForm.TasksList.Height,
WizardForm.TasksList.Width,
50
);
Warning.Font.Color := clRed;
Warning.Caption := 'Warning: This will result in a non-functional "Join in FrankenStein" button in the Tools Menu.';
end;
I used yet another amazing piece of code by TLama. The problem is I need the note to be visible when the user selects the task, and be hidden otherwise (while on the same page).
You have to handle WizardForm.TasksList.OnClickCheck event and update the Warning label visibility accordingly.
var
Warning: TNewStaticText;
procedure TasksListClickCheck(Sender: TObject);
begin
Warning.Visible :=
{ This (and the task index below) has to be kept in sync with the expression }
{ in "Components" parameter of the respective task. }
{ Though note that in your specific case the test }
{ is redundant as when "Slasher" is selected, you have no tasks, }
{ and the "Tasks" page is completely skipped, so you do not even get here. }
(not IsComponentSelected('Slasher')) and
WizardForm.TasksList.Checked[0]; { You can also use WizardIsTaskSelected }
end;
procedure InitializeWizard;
begin
Warning := TNewStaticText.Create(WizardForm);
...
{ Update Warning label visibility on task selection change }
WizardForm.TasksList.OnClickCheck := #TasksListClickCheck
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectTasks then
begin
{ Update initial visibility }
TasksListClickCheck(WizardForm.TasksList);
end;
end;
Side notes:
Do not hard code the height to fixed 50. Scale it with DPI instead: ScaleY(50).
You should set Warning.WordWrap := True as the caption does not fit page width.
You should shrink TasksList's height as the label does not fit below the list. You are missing the WizardForm.TasksList.Height := WizardForm.TasksList.Height - NoteHeight; from #TLama's code. Again note that he is missing the scaling of the NoteHeight.
const
NoteHeight = 50;
procedure InitializeWizard;
begin
WizardForm.TasksList.Height := WizardForm.TasksList.Height - ScaleY(NoteHeight);
Warning := TNewStaticText.Create(WizardForm);
Warning.Parent := WizardForm.SelectTasksPage;
Warning.AutoSize := False;
Warning.WordWrap := True;
Warning.SetBounds(
WizardForm.TasksList.Left,
WizardForm.TasksList.Top + WizardForm.TasksList.Height,
WizardForm.TasksList.Width,
ScaleY(NoteHeight)
);
Warning.Font.Color := clRed;
{ Update Warning label visibility on task selection change }
WizardForm.TasksList.OnClickCheck := #TasksListClickCheck
Warning.Caption :=
'Warning: This will result in a non-functional "Join in FrankenStein" button ' +
'in the Tools Menu.';
end;
I am new to Inno Setup and I have already read the documentation. Now I know that Inno Setup can accept different/custom parameter and could be processed via Pascal script. But the problem is, I don't know how to write in Pascal.
I am hoping I could get help about the coding.
I'd like to pass /NOSTART parameter to my setup file which while tell the setup to disable(uncheck) the check mark on "Launch " and if /NOSTART is not provided, it it will enable(check) the check mark "Launch "
or if possible, that Launch page is not required and do everything via code.
Since you can't imperatively modify flags for section entries and directly accessing the RunList would be quite a dirty workaround, I'm using for this two postinstall entries, while one has no unchecked flag specified and the second one has. So, the first entry represents the checked launch check box and the second one unchecked launch check box. Which one is used is controlled by the Check parameter function, where is checked if a command line tail contains /NOSTART parameter.
Also, I've used a little more straightforward function for determining if a certain parameter is contained in the command line tail. It uses the CompareText function to compare text in a case insensitive way. You can replace it with CompareStr function, if you want to compare the parameter text in a case sensitive way. Here is the script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[Run]
Filename: "calc.exe"; Description: "Launch calculator"; \
Flags: postinstall nowait skipifsilent; Check: LaunchChecked
Filename: "calc.exe"; Description: "Launch calculator"; \
Flags: postinstall nowait skipifsilent unchecked; Check: not LaunchChecked
[Code]
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
function LaunchChecked: Boolean;
begin
Result := not CmdLineParamExists('/NOSTART');
end;
and so a little research read and read .. i got my answer.
here's my code (except the "GetCommandLineParam")
[Code]
{
var
StartNow: Boolean;
}
function GetCommandLineParam(inParam: String): String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;
{
function InitializeSetup(): Boolean;
var
NOSTART_Value : String;
begin
NOSTART_Value := GetCommandLineParam('/NOSTART');
if(NOSTART_Value = 'false') then
begin
StartNow := True
end
else
begin
StartNow := False
end;
Result := True;
end;
}
procedure CurStepChanged(CurStep: TSetupStep);
var
Filename: String;
ResultCode: Integer;
NOSTART_Value : String;
begin
if CurStep = ssDone then
begin
NOSTART_Value := GetCommandLineParam('/NOSTART');
if(NOSTART_Value = 'false') then
begin
Filename := ExpandConstant('{app}\{#MyAppExeName}');
Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
end
end;
end;
a code update. Thanks to #TLama
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Break;
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
Filename: String;
ResultCode: Integer;
NOSTART_Value : String;
RunApp : Boolean;
begin
if CurStep = ssDone then
begin
RunApp := CmdLineParamExists('/START');
if(RunApp = True) then
begin
Filename := ExpandConstant('{app}\{#MyAppExeName}');
Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
end
// NOSTART_Value := GetCommandLineParam('/START');
// if(NOSTART_Value = 'true') then
// begin
// Filename := ExpandConstant('{app}\{#MyAppExeName}');
// Exec(Filename, '', '', SW_SHOW, ewNoWait, Resultcode);
//end
end;
end;
How about the following, easy to read
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Install Specialty Programs"
#define MyAppVersion "1.0"
#define MyAppPublisher ""
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{5}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
DefaultDirName={pf}\{#MyAppName}
DisableDirPage=yes
DefaultGroupName={#MyAppName}
DisableProgramGroupPage=yes
OutputDir=P:\_Development\INNO Setup Files\Specialty File Install
OutputBaseFilename=Specialty File Install
Compression=lzma
SolidCompression=yes
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "P:\_Development\INNO Setup Files\Specialty File Install\Files\0.0 - Steps.docx"; DestDir: "c:\support\Specialty Files"; Tasks: V00Step
[Tasks]
Name: "Office2013"; Description: "Running Office 2013"; Flags: checkablealone unchecked
Name: "Office2016"; Description: "Running Office 2016"; Flags: checkablealone unchecked
Name: "V00Step"; Description: "Steps To Follow (Read Me)"; Flags: exclusive
[Run]
Filename: "C:\Program Files (x86)\Microsoft Office\Office15\WINWORD.EXE"; Parameters: """c:\support\Specialty Files\0.0 - Steps.docx"""; Description: "Run if Office 2013 is installed"; Tasks: V00Step AND Office2013
Filename: "C:\Program Files (x86)\Microsoft Office\Office16\WINWORD.EXE"; Parameters: """c:\support\Specialty Files\0.0 - Steps.docx"""; Description: "Run if Office 2016 is installed"; Tasks: V00Step AND Office2016
I want to install files in different folders, depending on whether the user has selected to install for all users or just the current user.
I have added used CreateInputOptionPage() to create an option page with two radio buttons.
However, my installer is now littered with lots of duplicate lines, like these two:
Source: {#ProjectRootFolder}\License.txt; DestDir: {userdocs}\{#MyAppName}; Check: NOT IsAllUsers
Source: {#ProjectRootFolder}\License.txt; DestDir: {commondocs}\{#MyAppName}; Check:IsAllUsers
Is there a more elegant way to do the above? Can Pascal code, for example, create a variable like #define does so I can use it in place of {userdocs} and {commondocs} above?
Further details:
The IsAllUsers() function above calls this code:
function IsAllUsers: Boolean;
begin
#ifdef UPDATE
Result := AllUsersInRegistryIsTRUE;
#else
Result := AllUsersOrCurrentUserPage.Values[1]; // wizard page second radio button
#endif
end;
and:
function AllUsersInRegistryIsTRUE: Boolean; // True if preceding install was to all users' documents
var
AllUsersRegValue: AnsiString;
begin
if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'Software\MyApp', 'AllUsers', AllUsersRegValue) then
Result := (UpperCase(AllUsersRegValue) = 'YES')
else
Result := FALSE;
end;
Will something like this suit?
[Files]
Source: {#ProjectRootFolder}\License.txt; DestDir: {code:GetDir}\{#MyAppName};
...
[Code]
var
OptionsPage: TInputOptionWizardPage;
procedure InitializeWizard;
begin
OptionsPage := CreateInputOptionPage(wpUserInfo,
'please select', 'the kind of installation', 'and continue..',
True, False);
OptionsPage.Add('All users');
OptionsPage.Values[0] := True;
OptionsPage.Add('This user');
end;
function GetDir(Dummy: string): string;
begin
if OptionsPage.Values[0] then
Result := ExpandConstant('{commondocs}')
else
Result := ExpandConstant('{userdocs}');
end;