Reading values from custom Inno Setup wizard pages without using global variables - inno-setup

On this support page for creating a custom CreateInputOptionPage, they suggest storing the values of the page by just assigning them to a variable. It's not clear however WHEN this assignment should happen.'
From what I can tell, if you assign this right when you create the page, you will get the default value. This makes sense as when the page is created, user hasn't input any "Input Query"'s yet.
Therefore, I reasoned to assign the values from the page to a variable when the 'Next' button is clicked, using function NextButtonClick(CurPageID: Integer): Boolean;
In order to do that, I needed to access the page's variable (Page.Values[0]) in the NextButtonClick function. Since Page was defined in a different function, is the only way to access those values to have Page be a global variable? That's what I've resolved to do but I was wondering if anyone out there had an alternative to global variables.
Stub of my code so far.
[Code]
var
Page: TInputOptionWizardPage;
InstallationTypeIsClient: boolean;
procedure InitializeWizard();
begin
Page := CreateInputOptionPage(wpWelcome,'Installation Type', 'Select Installation Type', 'No really, do some selecting', True, False)
Page.Add('Server Install');
Page.Add('Client Install');
Page.Values[1] := True;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID=100 then
begin
InstallationTypeIsClient := Page.Values[1];
MsgBox('InstallationTypeIsClient value is ' + Format('%d', [InstallationTypeIsClient]), mbInformation, MB_OK);
end;
Result := True;
end;

Using a global variable to store the reference to the custom page is the correct and the easiest way.
Related question: Inno Setup: Access to custom control from OnClick event of another control.
Though it's questionable whether you really need to store the user value to another variable. Just read the value from the custom page at the moment you need it.
Using global variables in indeed frowned upon, in general. But that's, when you are developing a standalone code. In this case, you are just implementing event hooks for an existing application, so you have no other choice.
The only other way is to recursively lookup the custom page in child controls of the WizardForm. It's lot of code and quite inefficient.
See my answer to Inno Setup: OnHover event for an example of recursive component iteration.

Related

Inno Setup: add fields to InputQueryPage depending on components selected

I have an input query page, which asks the user for ports depending on what components are selected.
procedure InitializeWizard;
begin
{ Create the page }
ConfigPage :=
CreateInputQueryPage(
wpSelectComponents, 'User input', 'User input',
'Please specify the following information, then click Next.');
{ Add items (False means it's not a password edit) }
if IsComponentSelected('broker') then
begin
ConfigPage.Add('MQTT Broker port:', False);
{ Set initial values (optional) }
ConfigPage.Values[0] := ExpandConstant('1883');
end;
end;
The issue I am finding here is that InitializeWizard is always called prior to component selection, so IsComponentSelected('broker') is always called.
Is there any way around this? How can I achieve this?
It's not easy. For a really generic solution, you would have to create the custom page only when leaving the components selection page (e.g. in NextButtonClick with CurPageID equal wpSelectComponents). That's doable, but Inno Setup allows user to go back to the components page. When the user does that, you would have to remove the custom page, re-creating it later. But you would lose the data the user might have entered on the custom page. So you would have to persist the data somehow. All that is doable, but lot of work.
If it is about few input boxes only, consider instead disabling the boxes that are not relevant for the current components selection:
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = ConfigPage.ID then
begin
ConfigPage.Edits[0].Enabled := IsComponentSelected('foo');
ConfigPage.PromptLabels[0].Enabled := ConfigPage.Edits[0].Enabled;
ConfigPage.Edits[1].Enabled := IsComponentSelected('bar');
ConfigPage.PromptLabels[1].Enabled := ConfigPage.Edits[1].Enabled;
end;
end;
A step further would be to hide the boxes instead of disabling them (use Visible instead of Enabled). But then you would have to rearrange the boxes. But that's still easier then re-building the page.

Inno Setup: How to display license page before custom page shows from procedure InitializeWizard();

I am using LicenseFile=D:\authorized\Builds\Integration\License.rtf to display license page and procedure InitializeWizard();.
The Problem is that the license page is displayed after the procedure InitializeWizard();. Is there any way we can display it before?
procedure InitializeWizard;
begin
{ Create the pages }
UsagePage := CreateInputOptionPage(wpWelcome,
'App setup information', 'How would you like to install App?',
'Would you like to install App as a service?.',
True, False);
UsagePage.Add('Yes');
UsagePage.Add('No');
UsagePage.Values[0] := true;
end;
It's a misunderstanding. The InitializeWizard function does not display anything. It just creates the custom page(s), it does not display them.
Try adding a MsgBox call at the end of the function. You will see that the message displays before the wizard form even pops up.
The order of the custom pages is determined by the AfterID parameter (the first one) of the Create*Page functions.
If you want the custom page to show after the license page, use wpLicense, instead of wpWelcome.
UsagePage := CreateInputOptionPage(wpLicense, ...);

In Inno Setup, how can I set which component gets an initial focus on "Select Components" page

When the wpSelectComponents page is shown, I'd like to set focus to a particular item/component in the list. Is there a way to do this?
Set ItemIndex property of WizardForm.ComponentsList, like:
WizardForm.ComponentsList.ItemIndex := 2;
Note that focus of the list item is not rendered, until the list itself receives a focus:
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectComponents then
begin
WizardForm.ActiveControl := WizardForm.ComponentsList;
end;
end;
You need to use WinAPI to achieve this.
Unfortunately I could not find any Pascal example so have a look on this C++ code: Selecting and Highlighting an Item from List View.
Calling this code from Inno Setup requires to call WinApi dlls functions so maybe writing this feature as small C++ plugin could be easier.

In Inno Setup: How do you use/know the values for CurPageIDs for custom pages?

Lets say we set up a page using the CreateInputQueryPage function and we use the NextButtonClick event to control actions taken on that page. How do we refer to the correct CurPageID within that code?
First initialize your page as a global:
...
[code]
var
UserPage: TInputQueryWizardPage;
...
Then refer to that page variable (for example within the NextButtonClick event handler) like so:
UserPage.ID
See the example file CodeDlg.iss for an a complete example with the variable use in evidence.

cancel button not displayed for wizard page created with wpInfoAfter in inno setup

I have created a custom wizard page in inno, which needs to be shown after installing the files to {app} folder. This is achieved by giving the wpInfoAfter.
The problem is, its showing only the 'next' button, there is no cancel/back button, also the dialog's closing button on top right is also disabled. I understand that the back button isn't needed, as it needs to remove the files which are installed. Is there anyway the 'cancel' button can be displayed?
The Cancel button has no functionality at the after install stage, because InnoSetup doesn't expect to do further actions, that would require cancel, after the installation process is done. So, even if you show the button against that fact, you would get a button without any action.
Personally I would prefer to collect the information needed to setup your database before the installation starts, because consider the situation when the user installs your application and simply cancel the after installation wizard (what can easily happen). Doing it before, you'll be able to force your users to fill what you need before they actually get to the application itself. But if you still want to do it after install, here is a workaround for that missing cancel button.
As a workaround, you can create your own custom button, that will be on a same position with the same functionality. Here is a sample script, simulating a cancel button and showing it only on the custom page which is laying after the installation process. It's just a workaround, because you'd need at least fix this:
enable the closing cross of the wizard form (it's disabled after the installation stage is done)
handle the ESC key shortcut somehow (it invokes the exit prompt dialog too, but I couldn't find a way how to workaround this)
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
procedure ExitProcess(uExitCode: UINT);
external 'ExitProcess#kernel32.dll stdcall';
var
CustomPage: TWizardPage;
CancelButton: TNewButton;
procedure OnCancelButtonClick(Sender: TObject);
begin
// confirmation "Exit setup ?" message, if user accept, then...
if ExitSetupMsgBox then
begin
// stop and rollback actions you did from your after install
// process and kill the setup process itself
ExitProcess(0);
end;
end;
procedure InitializeWizard;
begin
// create a custom page
CustomPage := CreateCustomPage(wpInfoAfter, 'Caption', 'Description');
// create a cancel button, set its parent, hide it, setup the bounds
// and caption by the original and assign the click event
CancelButton := TNewButton.Create(WizardForm);
CancelButton.Parent := WizardForm;
CancelButton.Visible := False;
CancelButton.SetBounds(
WizardForm.CancelButton.Left,
WizardForm.CancelButton.Top,
WizardForm.CancelButton.Width,
WizardForm.CancelButton.Height
);
CancelButton.Caption := SetupMessage(msgButtonCancel);
CancelButton.OnClick := #OnCancelButtonClick;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
// show your fake Cancel button only when you're on some of your after
// install pages; if you have more pages use something like this
// CancelButton.Visible := (CurPageID >= FirstPage.ID) and
// (CurPageID <= LastPage.ID);
// if you have just one page, use the following instead
CancelButton.Visible := CurPageID = CustomPage.ID;
end;

Resources