Unpin app from taskbar, startmenu using Inno Setup - inno-setup

I am developing an installer using Inno Setup targeting XP, Win7, 8. I need the app icon to be pinned to taskbar and the startmenu. So far I have been able to do that.
Now, when the user uninstalls this program, the pinned items should be unpinned. I haven't managed to find a solution to this.
Please guide.

You've said that you have used a function from this link. I assume the one from this post:
procedure zylPinAppToTaskbar(strPath, strApp: string);
var
vShell, vFolder, vFolderItem, vItemVerbs: Variant;
vPath, vApp: Variant;
i: Integer;
sItem: String;
h: LongInt;
szPinName: String;
filenameEnd : Integer;
filename : String;
strEnd : String;
begin
SetLength(szPinName, 255);
h := LoadLibrary(ExpandConstant('{sys}\Shell32.dll'));
LoadString(h, 5386, szPinName, 255);
FreeLibrary(h);
strEnd := #0;
filenameEnd := Pos(strEnd, szPinName);
filename := Copy(szPinName, 1, filenameEnd - 1);
if (Length(filename) > 0) then //WinXp or lower, no pin taskbar function
begin
vShell := CreateOleObject('Shell.Application');
vPath := strPath;
vFolder := vShell.NameSpace(vPath);
vApp := strApp;
vFolderItem := vFolder.ParseName(vApp);
vItemVerbs := vFolderItem.Verbs;
for i := 1 to vItemVerbs.Count do
begin
sItem := vItemVerbs.Item(i).Name;
if (sItem = filename) then
begin
// 63 63 72 75 6E 2E 63 6F 6D
vItemVerbs.Item(i).DoIt;
break;
end;
end;
end;
end;
That's really hacky way (which I wouldn't rely on). Let's focus now on what it actually does. The function loads the Shell32.dll library and reads from its string table the caption of the popup menu item that belongs to the Pin this program to taskbar feature (and stores it into the filename variable). Then it connects to Shell and creates the Folder object for the passed folder path (vFolder variable). For this folder object it then creates the FolderItem object (vFolderItem variable) and on this object iterates all the available verbs (vItemVerbs variable) and checks if the Name matches the one read from the Shell32.dll library. If it finds one, it invokes the action by the DoIt method and breaks the iteration.
Now if you know what the above code does, you can guess that the only thing you need to do to perform the unpin action is finding the caption of the popup menu item for that feature. I've looked into the string table of the Shell32.dll library and the Unpin this program from taskbar string has ID 5387, so the only thing to modify the above function for unpinning is changing this line:
// this magical 5386 value is the ID of the "Pin this program to taskbar"
// popup menu caption string in the Shell32.dll string table
LoadString(h, 5386, szPinName, 255);
To this:
// this magical 5387 value is the ID of the "Unpin this program from taskbar"
// popup menu caption string in the Shell32.dll string table
LoadString(h, 5387, szPinName, 255);
But I do not recommend that way. There is no official way to pin program to taskbar because that's been reserved for the user to decide.
As a bonus, I wrote the following wrapper for the above code:
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
// these constants are not defined in Windows
SHELL32_STRING_ID_PIN_TO_TASKBAR = 5386;
SHELL32_STRING_ID_PIN_TO_STARTMENU = 5381;
SHELL32_STRING_ID_UNPIN_FROM_TASKBAR = 5387;
SHELL32_STRING_ID_UNPIN_FROM_STARTMENU = 5382;
type
HINSTANCE = THandle;
HMODULE = HINSTANCE;
TPinDest = (
pdTaskbar,
pdStartMenu
);
function LoadLibrary(lpFileName: string): HMODULE;
external 'LoadLibrary{#AW}#kernel32.dll stdcall';
function FreeLibrary(hModule: HMODULE): BOOL;
external 'FreeLibrary#kernel32.dll stdcall';
function LoadString(hInstance: HINSTANCE; uID: UINT;
lpBuffer: string; nBufferMax: Integer): Integer;
external 'LoadString{#AW}#user32.dll stdcall';
function TryGetVerbName(ID: UINT; out VerbName: string): Boolean;
var
Buffer: string;
BufLen: Integer;
Handle: HMODULE;
begin
Result := False;
Handle := LoadLibrary(ExpandConstant('{sys}\Shell32.dll'));
if Handle <> 0 then
try
SetLength(Buffer, 255);
BufLen := LoadString(Handle, ID, Buffer, Length(Buffer));
if BufLen <> 0 then
begin
Result := True;
VerbName := Copy(Buffer, 1, BufLen);
end;
finally
FreeLibrary(Handle);
end;
end;
function ExecVerb(const FileName, VerbName: string): Boolean;
var
I: Integer;
Shell: Variant;
Folder: Variant;
FolderItem: Variant;
begin
Result := False;
Shell := CreateOleObject('Shell.Application');
Folder := Shell.NameSpace(ExtractFilePath(FileName));
FolderItem := Folder.ParseName(ExtractFileName(FileName));
for I := 1 to FolderItem.Verbs.Count do
begin
if FolderItem.Verbs.Item(I).Name = VerbName then
begin
FolderItem.Verbs.Item(I).DoIt;
Result := True;
Exit;
end;
end;
end;
function PinAppTo(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_PIN_TO_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_PIN_TO_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
function UnpinAppFrom(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
And its possible usage, for pinning:
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc has been pinned to the taskbar.', mbInformation, MB_OK);
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc has been pinned to the start menu.', mbInformation, MB_OK);
And for unpinning:
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc is not pinned to the taskbar anymore.', mbInformation, MB_OK);
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc is not pinned to the start menu anymore.', mbInformation, MB_OK);

I was able to do it. Here's the code to pin and unpin from the startmenu and taskbar.
oShell := CreateOleObject('Shell.Application');
objFolder := oShell.Namespace(ExpandConstant('{localappdata}\My_Path'));
objFolderItem := objFolder.ParseName('MyApp.exe');
colVerbs := objFolderItem.Verbs();
for i := 0 to colverbs.count() do
begin
VerbName := lowercase(colverbs.item(i).name);
StringChangeEx(VerbName,'&','',true);
if (CompareText(Verbname, 'Pin to Start Menu') = 0) then
colverbs.item(i).DoIt
if (CompareText(Verbname, 'Pin to Taskbar') = 0) then
colverbs.item(i).DoIt
end;
Change the compare string to 'Unpin from Start Menu' and 'Unpin from Taskbar' at the time of unpinning.

Related

Inno setup: Extract XML node and show it

I have the following code and I want to extract it into a plain text but I cannot manage to do it.
The code works but what I need is to show it in the ExpandConstant field.
I have tried several ways but no luck so far.
function LoadValueFromXML(const AFileName, APath: string): string;
var
XMLNode: Variant;
XMLDocument: Variant;
begin
Result := '';
XMLDocument := CreateOleObject('Msxml2.DOMDocument.3.0');
try
XMLDocument.async := False;
XMLDocument.load(AFileName);
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(APath);
Result := XMLNode.text;
end;
except
MsgBox('An error occured!' + #13#10 + GetExceptionMessage, mbError, MB_OK);
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = CustomPageID then
CustomEdit.Text := LoadValueFromXML('C:\Games\World_of_Tanks_test\WoTLauncher.xml', '//info/patch_info_urls/item');
end;
procedure ClienteWot();
var
StaticText: TNewStaticText;
begin
StaticText := TNewStaticText.Create(WizardForm);
StaticText.Parent := WizardForm.SelectComponentsPage;
StaticText.Left := 425;
StaticText.Top := ScaleY(40);
StaticText.Font.Style := [fsBold];
//StaticText.Font.Color := clRed;
StaticText.Caption := ExpandConstant('Cliente WOT: -->>> Show XML Url <<<---');
end;
If you want to inline a value into a string that can be passed to the ExpandConstant function call, you can use e.g. the Format function:
var
...
URL: string;
begin
...
URL := LoadValueFromXML('C:\MyFile.xml', '//node/subnode');
StaticText.Caption := ExpandConstant(Format('{username} %s', [URL]));
end;
The above pseudocode example reads the XML value and assigns the returned value to the URL variable. Then it evaluates the inner statement of the second line:
Format('{username} %s', [URL])
Which inlines the URL string value into the given string (in place of %s) and produces a string like:
'{username} www.example.com'
And this string is then processed by the ExpandConstant function call (the outer statement) and assigned to the static text caption, which might be e.g.:
'MyUserName www.example.com'

InnoSetup: how to center setup window

I based my setup in
Larger "Select Components" page in Inno Setup . I would like to know how to center the setup window when I go to components page, since in small resolutions bottom buttons are not visible.
Currently, there is no function to let you center the wizard form only in vertical direction. So, to make one you need to code a little bit. Here is function, that allows you to center forms in direction that you choose on the nearest monitor that the form covers the most:
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HMONITOR = THandle;
TMonitorInfo = record
cbSize: DWORD;
rcMonitor: TRect;
rcWork: TRect;
dwFlags: DWORD;
end;
const
MONITOR_DEFAULTTONULL = $0;
MONITOR_DEFAULTTOPRIMARY = $1;
MONITOR_DEFAULTTONEAREST = $2;
function GetMonitorInfo(hMonitor: HMONITOR; out lpmi: TMonitorInfo): BOOL;
external 'GetMonitorInfo{#AW}#user32.dll stdcall';
function MonitorFromWindow(hwnd: HWND; dwFlags: DWORD): HMONITOR;
external 'MonitorFromWindow#user32.dll stdcall';
procedure CenterForm(Form: TForm; Horz, Vert: Boolean);
var
X, Y: Integer;
Monitor: HMONITOR;
MonitorInfo: TMonitorInfo;
begin
if not (Horz or Vert) then
Exit;
Monitor := MonitorFromWindow(Form.Handle, MONITOR_DEFAULTTONEAREST);
MonitorInfo.cbSize := SizeOf(MonitorInfo);
if GetMonitorInfo(Monitor, MonitorInfo) then
begin
if not Horz then
X := Form.Left
else
X := MonitorInfo.rcWork.Left + ((MonitorInfo.rcWork.Right -
MonitorInfo.rcWork.Left) - Form.Width) div 2;
if not Vert then
Y := Form.Top
else
Y := MonitorInfo.rcWork.Top + ((MonitorInfo.rcWork.Bottom -
MonitorInfo.rcWork.Top) - Form.Height) div 2;
Form.SetBounds(X, Y, Form.Width, Form.Height);
end;
end;
To implement it in the code you used, you need to modify the part when the page is being changed:
...
procedure CurPageChanged(CurPageID: Integer);
begin
if CurpageID = wpSelectComponents then
begin
SaveComponentsPage(CompPagePositions);
LoadComponentsPage(CompPagePositions, 200);
CenterForm(WizardForm, False, True); // <- center the form only vertically
CompPageModified := True;
end
else
if CompPageModified then
begin
LoadComponentsPage(CompPagePositions, 0);
CenterForm(WizardForm, False, True); // <- center the form only vertically
CompPageModified := False;
end;
end;

Inno Setup: Allowing the user to only choose the drive the software can be installed?

Can I allow the user to only choose the drive in which the software will be installed?
For example they can choose the C or D drive:
C:\Software
D:\Software
But the user can not specify anything else,
Like they can't choose to install the software under Downloads or MyDocumnets … etc.
Is this possible?
How to restrict users to select only drive on which the software will be installed ?
There are hundreds of ways to design this restriction. I chose the one which creates a combo box with available paths that user can choose from. This code as first lists all fixed drives on the machine, and if there's at least one, it creates the combo box which is placed instead of original dir selection controls. It is filled with drive names followed by a fixed directory taken from the DefaultDirName directive value which must not contain a drive portion since it is already concatenated with found fixed drive roots:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName=My Program
[Messages]
SelectDirBrowseLabel=To continue, click Next.
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
TDriveType = (
dtUnknown,
dtNoRootDir,
dtRemovable,
dtFixed,
dtRemote,
dtCDROM,
dtRAMDisk
);
TDriveTypes = set of TDriveType;
function GetDriveType(lpRootPathName: string): UINT;
external 'GetDriveType{#AW}#kernel32.dll stdcall';
function GetLogicalDriveStrings(nBufferLength: DWORD; lpBuffer: string): DWORD;
external 'GetLogicalDriveStrings{#AW}#kernel32.dll stdcall';
var
DirCombo: TNewComboBox;
#ifndef UNICODE
function IntToDriveType(Value: UINT): TDriveType;
begin
Result := dtUnknown;
case Value of
1: Result := dtNoRootDir;
2: Result := dtRemovable;
3: Result := dtFixed;
4: Result := dtRemote;
5: Result := dtCDROM;
6: Result := dtRAMDisk;
end;
end;
#endif
function GetLogicalDrives(Filter: TDriveTypes; Drives: TStrings): Integer;
var
S: string;
I: Integer;
DriveRoot: string;
begin
Result := 0;
I := GetLogicalDriveStrings(0, #0);
if I > 0 then
begin
SetLength(S, I);
if GetLogicalDriveStrings(Length(S), S) > 0 then
begin
S := TrimRight(S) + #0;
I := Pos(#0, S);
while I > 0 do
begin
DriveRoot := Copy(S, 1, I - 1);
#ifdef UNICODE
if (Filter = []) or
(TDriveType(GetDriveType(DriveRoot)) in Filter) then
#else
if (Filter = []) or
(IntToDriveType(GetDriveType(DriveRoot)) in Filter) then
#endif
begin
Drives.Add(DriveRoot);
end;
Delete(S, 1, I);
I := Pos(#0, S);
end;
Result := Drives.Count;
end;
end;
end;
procedure DriveComboChange(Sender: TObject);
begin
WizardForm.DirEdit.Text := DirCombo.Text;
end;
procedure InitializeWizard;
var
I: Integer;
StringList: TStringList;
begin
StringList := TStringList.Create;
try
if GetLogicalDrives([dtFixed], StringList) > 0 then
begin
WizardForm.DirEdit.Visible := False;
WizardForm.DirBrowseButton.Visible := False;
DirCombo := TNewComboBox.Create(WizardForm);
DirCombo.Parent := WizardForm.DirEdit.Parent;
DirCombo.SetBounds(WizardForm.DirEdit.Left, WizardForm.DirEdit.Top,
WizardForm.DirBrowseButton.Left + WizardForm.DirBrowseButton.Width -
WizardForm.DirEdit.Left, WizardForm.DirEdit.Height);
DirCombo.Style := csDropDownList;
DirCombo.OnChange := #DriveComboChange;
for I := 0 to StringList.Count - 1 do
DirCombo.Items.Add(StringList[I] + '{#SetupSetting('DefaultDirName')}');
DirCombo.ItemIndex := 0;
DirCombo.OnChange(nil);
end;
finally
StringList.Free;
end;
end;
And a screenshot:

Inno Setup Search for specifc file on a CD, retrieve exact filepath and return value to [Files]-Section [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I want to make an installer for an old program which comes on 2 CDs and I want to install the files directly from the discs.
At start up the setup should check if a certain file exists which means the first CD is inserted into the cd rom drive.
This is the code for that task:
[Files]
Source: {code: ??? }; Destination: {app}; flags:external;
[Code]
procedure InitializeWizard();
begin
if not FileExists('A:\Resource\CD1.GOB') xor
FileExists('B:\Resource\CD1.GOB') xor
// and so on, for every drive letter...
FileExists('Z:\Resource\CD1.GOB') then
Repeat
if MsgBox('Insert the first CD!', mbInformation, MB_OKCANCEL) = IDCANCEL then
ExitProcess(0);
Until FileExists('A:\Resource\CD1.GOB') or
FileExists('B:\Resource\CD1.GOB') or
// going through all letters again...
FileExists('Z:\Resource\CD1.GOB') = true;
So this works as intended. If the CD is not inserted and thus the file cannot be found a message will be shown which asks the user to insert the CD.
But I am wondering if there is a better way to increment the drive letter, because this is quite a mess.
And second, how can I save the full filepath und pass it on to the [Files] section?
I hope you can help me with this!
UPDATE:
I tried it again and came up with this:
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageId = wpWelcome then
begin
WizardForm.NextButton.Enabled := False;
repeat
for i:=0 to 31 do
dstr := (Chr(Ord('A') + i) + ':\Resource\CD1.gob');
until FileExists(dstr);
WizardForm.NextButton.Enabled := True;
end;
end;
But using this code Setup freezes at the beginning and doesn't respond even if the CD is already inserted.
Something like this should do what you need:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Files]
Source: {code:GetFileSource}; DestDir: {app}; flags:external;
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
TDriveType = (
dtUnknown,
dtNoRootDir,
dtRemovable,
dtFixed,
dtRemote,
dtCDROM,
dtRAMDisk
);
TDriveTypes = set of TDriveType;
function GetDriveType(lpRootPathName: string): UINT;
external 'GetDriveType{#AW}#kernel32.dll stdcall';
function GetLogicalDriveStrings(nBufferLength: DWORD; lpBuffer: string): DWORD;
external 'GetLogicalDriveStrings{#AW}#kernel32.dll stdcall';
var
FileSource: string;
#ifndef UNICODE
function IntToDriveType(Value: UINT): TDriveType;
begin
Result := dtUnknown;
case Value of
1: Result := dtNoRootDir;
2: Result := dtRemovable;
3: Result := dtFixed;
4: Result := dtRemote;
5: Result := dtCDROM;
6: Result := dtRAMDisk;
end;
end;
#endif
function GetLogicalDrives(var ADrives: array of string;
AFilter: TDriveTypes): Integer;
var
S: string;
I: Integer;
DriveRoot: string;
begin
Result := 0;
SetArrayLength(ADrives, 0);
I := GetLogicalDriveStrings(0, #0);
if I > 0 then
begin
SetLength(S, I);
if GetLogicalDriveStrings(Length(S), S) > 0 then
begin
S := TrimRight(S);
I := Pos(#0, S);
while I > 0 do
begin
DriveRoot := Copy(S, 1, I - 1);
#ifdef UNICODE
if (AFilter = []) or
(TDriveType(GetDriveType(DriveRoot)) in AFilter) then
#else
if (AFilter = []) or
(IntToDriveType(GetDriveType(DriveRoot)) in AFilter) then
#endif
begin
SetArrayLength(ADrives, GetArrayLength(ADrives) + 1);
#ifdef UNICODE
ADrives[High(ADrives)] := DriveRoot;
#else
ADrives[GetArrayLength(ADrives) - 1] := DriveRoot;
#endif
end;
Delete(S, 1, I);
I := Pos(#0, S);
end;
Result := GetArrayLength(ADrives);
end;
end;
end;
function GetFileSource(Value: string): string;
begin
// file source path passed to the [Files] section
Result := FileSource;
end;
procedure InitializeWizard;
var
I: Integer;
DriveCount: Integer;
DriveArray: array of string;
begin
// the function will fill the DriveArray only with CDROM
// drives and returns the count of found drives
DriveCount := GetLogicalDrives(DriveArray, [dtCDROM]);
// here you have an array of CD-ROM drives so iterate it
// search for a file you need and when you find it, pass
// the path to the FileSource variable, which will later
// be queried to get the source to the file in [Files]
for I := 0 to DriveCount - 1 do
begin
if FileExists(DriveArray[I] + 'Resource\CD1.GOB') then
begin
FileSource := DriveArray[I] + 'Resource\CD1.GOB';
Break;
end;
end;
MsgBox('File was found on path: ' + FileSource, mbInformation, MB_OK);
end;

CustomPage for Serial Number in Inno Setup

How to create CustomPage in Inno Setup with Edit Boxes for Serial Number?
E.g. 6x5chars or 7x5chars?
Script should check if all boxes are filled before Next button become available.
It would be also good if there could be Copy/Paste function implemented that would allow to fill up all Edit Boxes if the clipboard content matches the serial number pattern.
Here is one approach that uses the custom page where the separate edit boxes are created. You only need to specify the value for the SC_EDITCOUNT constant where the number of edit boxes is defined and the SC_CHARCOUNT what is the number of characters that can be entered into these edit boxes. If you are in the first edit box you may paste the whole serial number if it's in the format by the pattern delimited by the - char (the TryPasteSerialNumber function here). To get the serial number from the edit boxes it's enough to call GetSerialNumber where you can specify also a delimiter for the output format (if needed).
[Setup]
AppName=Serial number project
AppVersion=1.0
DefaultDirName={pf}\Serial number project
[code]
function SetFocus(hWnd: HWND): HWND;
external 'SetFocus#user32.dll stdcall';
function OpenClipboard(hWndNewOwner: HWND): BOOL;
external 'OpenClipboard#user32.dll stdcall';
function GetClipboardData(uFormat: UINT): THandle;
external 'GetClipboardData#user32.dll stdcall';
function CloseClipboard: BOOL;
external 'CloseClipboard#user32.dll stdcall';
function GlobalLock(hMem: THandle): PAnsiChar;
external 'GlobalLock#kernel32.dll stdcall';
function GlobalUnlock(hMem: THandle): BOOL;
external 'GlobalUnlock#kernel32.dll stdcall';
var
SerialPage: TWizardPage;
SerialEdits: array of TEdit;
const
CF_TEXT = 1;
VK_BACK = 8;
SC_EDITCOUNT = 6;
SC_CHARCOUNT = 5;
SC_DELIMITER = '-';
function IsValidInput: Boolean;
var
I: Integer;
begin
Result := True;
for I := 0 to GetArrayLength(SerialEdits) - 1 do
if Length(SerialEdits[I].Text) < SC_CHARCOUNT then
begin
Result := False;
Break;
end;
end;
function GetClipboardText: string;
var
Data: THandle;
begin
Result := '';
if OpenClipboard(0) then
try
Data := GetClipboardData(CF_TEXT);
if Data <> 0 then
Result := String(GlobalLock(Data));
finally
if Data <> 0 then
GlobalUnlock(Data);
CloseClipboard;
end;
end;
function GetSerialNumber(ADelimiter: Char): string;
var
I: Integer;
begin
Result := '';
for I := 0 to GetArrayLength(SerialEdits) - 1 do
Result := Result + SerialEdits[I].Text + ADelimiter;
Delete(Result, Length(Result), 1);
end;
function TrySetSerialNumber(const ASerialNumber: string; ADelimiter: Char): Boolean;
var
I: Integer;
J: Integer;
begin
Result := False;
if Length(ASerialNumber) = ((SC_EDITCOUNT * SC_CHARCOUNT) + (SC_EDITCOUNT - 1)) then
begin
for I := 1 to SC_EDITCOUNT - 1 do
if ASerialNumber[(I * SC_CHARCOUNT) + I] <> ADelimiter then
Exit;
for I := 0 to GetArrayLength(SerialEdits) - 1 do
begin
J := (I * SC_CHARCOUNT) + I + 1;
SerialEdits[I].Text := Copy(ASerialNumber, J, SC_CHARCOUNT);
end;
Result := True;
end;
end;
function TryPasteSerialNumber: Boolean;
begin
Result := TrySetSerialNumber(GetClipboardText, SC_DELIMITER);
end;
procedure OnSerialEditChange(Sender: TObject);
begin
WizardForm.NextButton.Enabled := IsValidInput;
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 valid serial number and continue 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 := IsValidInput;
end;
procedure InitializeWizard;
begin
CreateSerialNumberPage;
end;
And here is how it looks like:
You can make Inno prompt the user for a serial key by adding an CheckSerial() event function.
If you want more control over the page, you can use one of the stock pages (CreateInput...Page) or a custom page in the setup wizard using CreateCustomPage() and adding controls as you require.
See the codedlg.iss example included with Inno setup.
The simplest way to add a Serial key box, beneath the Name and Organisation text fields, is to add something like the following to your iss file.
[Code]
function CheckSerial(Serial: String): Boolean;
begin
// serial format is XXXX-XXXX-XXXX-XXXX
Serial := Trim(Serial);
if Length(Serial) = 19 then
result := true;
end;
This can be usefully combined with
[Setup]
DefaultUserInfoSerial={param:Serial}
which will fill in the serial if previously entered for the install.

Resources