Run a batch file only one time when the installer starts - inno-setup

I'm having an issue here with the following:
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
begin
Result := true;
if MsgBox('Wanna help?',mbConfirmation, MB_YESNO )= IDYES then
begin
CreateBatch();
Exec('cmd.exe', '/c "' +ExpandConstant('{tmp}\batch.bat'),'',SW_HIDE, ewWaitUntilTerminated, ResultCode);
Result:= false;
end;
end;
in the batch file i got the following :
#ECHO OFF
D:
cd D:\_INSTALLER\Output
"installer.exe" /SAVEINF="opt.txt"
So it basically re-open the installer , over and over again ... ( infinite loop )
Is there any way to ask : Wanna help? only for the 1st time ? and if the user clicked yes, the batch should be executed , else if the user clicked no it should continue the install normally.
Thanks in advance for the support ,
BeGiN.

With the help of TLama and his post i achieved my goal by using the following script:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
procedure ExitProcess(exitCode:integer);external 'ExitProcess#kernel32.dll stdcall';
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
var
withINF: Boolean;
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;
//Initialize setup
function InitializeSetup(): Boolean;
var
ResultCode:Integer;
Params: string;
RetVal: HINSTANCE;
begin
Result := true;
withINF := CmdLineParamExists('/SAVEINF=opt.txt');
if not withINF then
begin
Params := '/SAVEINF=opt.txt';
ShellExecute(0, 'open',ExpandConstant('{srcexe}'), Params, '', SW_SHOW);
ExitProcess(0);
end;
end;
L.E: A shorter code for doing the same thing also made by TLama(multilingual support added):
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
ShowLanguageDialog = yes
[Languages]
Name: "en"; MessagesFile: "compiler:Default.isl"
Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
type
HINSTANCE = THandle;
function ShellExecute(hwnd: HWND; lpOperation: string; lpFile: string;lpParameters: string; lpDirectory: string; nShowCmd: Integer): HINSTANCE;
external 'ShellExecute{#AW}#shell32.dll stdcall';
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;
function InitializeSetup: Boolean;
var
Params: string;
begin
// prepare Params variable for reusing
if ExpandConstant('{language}') = 'en' then begin
Params := '/SAVEINF=opt.txt /LANG=en';
end;
if ExpandConstant('{language}') = 'nl' then begin
Params := '/SAVEINF=opt.txt /LANG=nl';
end;
// allow this setup to run if the expected parameter is specified; or, if it is not, allow to run it
// when ShellExecute fails; it works like this - first evaluates the CmdLineParamExists function and
// if that returns True, the second part of the statement (ShellExecute) won't run (evaluate); when
// the parameter is not found, the CmdLineParamExists returns False and statement evaluation goes on,
// ShellExecute attempts to run the setup and to the Result returns True (allow this instance to run)
// when the function fails for some reason (the returned value <= 32); in other words, you will allow
// this setup instance to run if executing of the new setup instance fails
Result := CmdLineParamExists('/SAVEINF=opt.txt') or (ShellExecute(0, '', ExpandConstant('{srcexe}'), Params, '', SW_SHOW) <= 32);
end;
Regards,
BeGiN

I see not reason for this but you can simply solve this by creating temp file.
If user clicks Yes (first time) then create some file (any file with some random content) just before CreateBatch() is called.
Then simply check for existence of this file - if it exists user already clicked Yes (once) and do what ever you want.

Related

How can I safely check a main thread flag from another thread?

I found this code on the net and it's working but I'm not sure if it's ok to directly read the variable in the main thread from another thread. In this example the flag (variable) is CancelCopy.
In general, I want to know how can I read the state of a variable from the main thread in another thread but immediately, without waiting.
type
TCopyEx = packed record
Source: String;
Dest: String;
Handle: THandle;
end;
PCopyEx = ^TCopyEx;
const
CFEX_CANCEL = WM_USER + 1;
var
CancelCopy:Boolean=False;
function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize,
StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD;
hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
begin
if CancelCopy then begin
SendMessage(THandle(lpData), CFEX_CANCEL, 0, 0);
result:=PROGRESS_CANCEL;
Exit;
end;
//rest of the code here.......
end;
function CopyExThread(p: PCopyEx):Integer;
var
Source: String;
Dest: String;
Handle: THandle;
Cancel: PBool;
begin
Source:=p.Source;
Dest:=p.Dest;
Handle:=p.Handle;
Cancel:=PBOOL(False);
CopyFileEx(PChar(Source), PChar(Dest), #CopyFileProgress, Pointer(Handle), Cancel, COPY_FILE_NO_BUFFERING);
Dispose(p);
result:=0;
end;
procedure TFormMain.ButtonCopyClick(Sender: TObject);
var
Params: PCopyEx;
ThreadID: Cardinal;
begin
cancelCopy := False;
New(Params);
Params.Source := EditOriginal.Text;
Params.Dest := EditCopied.Text;
Params.Handle := Handle;
CloseHandle(BeginThread(nil, 0, #CopyExThread, Params, 0, ThreadID));
end;
procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
cancelCopy := true;
end;
Technically, the code you have shown is fine, and will work as expected.
However, there is a small mistake in it. You are passing the wrong pointer value to the pbCancel parameter of CopyFileEx(). However, your code does not crash because the pointer you are passing is effectively being set to nil, and pbCancel will accept a nil pointer, thus CopyFileEx() will ignore the parameter.
What you are supposed to do is pass the address of a BOOL variable, which you can set to TRUE at any time to cancel the copy. CopyFileEx() will monitor that variable for you, you do not need to manually return PROGRESS_CANCEL from the callback when the variable is set (return PROGRESS_CANCEL if your callback encounters an error not related to the copy itself, and you want to abort the copy as a result of the error). I would not use a global variable for that, though. I would use a variable that is local to the Form that is performing the copy.
Try something more like this instead:
type
TFormMain = class(TForm)
...
private
CancelCopy: BOOL; // <-- BOOL, not Boolean
...
end;
...
type
TCopyEx = record
Source: String;
Dest: String;
Handle: HWND;
PCancelCopy: PBOOL;
end;
PCopyEx = ^TCopyEx;
const
CFEX_CANCEL = WM_USER + 1;
function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize,
StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD;
hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
begin
// no need to watch CancelCopy here...
// do normal status handling here as needed...
// use PCopyEx(lpData)^ as needed...
end;
function CopyExThread(p: PCopyEx): Integer;
begin
try
if not CopyFileEx(PChar(p.Source), PChar(p.Dest), #CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then
begin
if GetLastError() = ERROR_REQUEST_ABORTED then
SendMessage(p.Handle, CFEX_CANCEL, 0, 0);
end;
finally
Dispose(p);
end;
Result := 0;
end;
procedure TFormMain.ButtonCopyClick(Sender: TObject);
var
Params: PCopyEx;
ThreadID: Cardinal;
begin
New(Params);
Params.Source := EditOriginal.Text;
Params.Dest := EditCopied.Text;
Params.Handle := Handle;
Params.PCancelCopy := #CancelCopy; // <-- pass address of CancelCopy here...
CancelCopy := FALSE;
CloseHandle(BeginThread(nil, 0, #CopyExThread, Params, 0, ThreadID));
end;
procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
CancelCopy := TRUE;
end;
With that said, something else to watch out for - you are passing the HWND from the TForm.Handle property to the thread. If the TForm ever destroys/recreates its HWND for any reason (and yes, it can happen) while the thread is still running, the TCopyEx.Handle value will be left pointing to an invalid window (or worse, to a new window that reuses the old HWND value).
In general, the TWinControl.Handle property is not thread-safe, and so for that reason alone, it is not a good idea to pass the HWND of a TWinControl object to a worker thread, unless you can guarantee the HWND will not be destroyed while the thread is running (and in this example, that is not guaranteed).
In this example, I would use a different HWND that is guaranteed to be persistent for the life of the thread, such as the TApplication.Handle window (messages sent to this window can be handled via TApplication.HookMainWindow()), or the result of calling AllocateHWnd().
For example:
type
TFormMain = class(TForm)
procedure FormDestroy(Sender: TObject);
...
private
CancelCopy: BOOL; // <-- BOOL, not Boolean
CopyFileExWnd: HWND;
procedure CopyFileExWndProc(var Message: TMessage);
...
end;
...
type
TCopyEx = record
Source: String;
Dest: String;
Handle: HWND;
PCancelCopy: PBOOL;
end;
PCopyEx = ^TCopyEx;
const
CFEX_CANCEL = WM_USER + 1;
function CopyFileProgress(TotalFileSize, TotalBytesTransferred, StreamSize,
StreamBytesTransferred: LARGE_INTEGER; dwStreamNumber, dwCallbackReason: DWORD;
hSourceFile, hDestinationFile: THandle; lpData: Pointer): DWORD; stdcall;
begin
...
end;
function CopyExThread(p: PCopyEx): Integer;
begin
try
if not CopyFileEx(
PChar(p.Source), PChar(p.Dest), #CopyFileProgress, p, p.PCancelCopy, COPY_FILE_NO_BUFFERING) then
begin
if GetLastError() = ERROR_REQUEST_ABORTED then
SendMessage(p.Handle, CFEX_CANCEL, 0, 0);
end;
finally
Dispose(p);
end;
Result := 0;
end;
procedure TFormMain.FormDestroy(Sender: TObject);
begin
if CopyFileExWnd <> 0 then
DeallocateHWnd(CopyFileExWnd);
end;
procedure TFormMain.ButtonCopyClick(Sender: TObject);
var
Params: PCopyEx;
ThreadID: Cardinal;
begin
if CopyFileExWnd = 0 then
CopyFileExWnd := AllocateHWnd(CopyFileExWndProc);
New(Params);
Params.Source := EditOriginal.Text;
Params.Dest := EditCopied.Text;
Params.Handle := CopyFileExWnd;
Params.PCancelCopy := #CancelCopy;
CancelCopy := FALSE;
CloseHandle(BeginThread(nil, 0, #CopyExThread, Params, 0, ThreadID));
end;
procedure TFormMain.ButtonCancelClick(Sender: TObject);
begin
CancelCopy := TRUE;
end;
procedure TFormMain.CopyFileExWndProc(var Message: TMessage);
begin
case Message.Msg of
CFEX_CANCEL: begin
...
end;
...
else
Message.Result := DefWindowProc(CopyFileExWnd, Message.Msg, Message.WParam, Message.LParam);
end;
end;

Inno Setup - How to add cancel button to decompressing page?

I am using this code: How to add .arc decompression to Inno Setup? (answer of Martin Prikryl). I want to add a cancel button at decompressing page and active this page for others functions (when the decompression is active, this page is inactive and, for example, i can not press on/off button of my music implementation).
How to add a cancel button to decompression page? and how to active this page for others functions?
I have reimplemented the solution from How to add .arc decompression to Inno Setup? using unarc.dll (from FreeArc+InnoSetup package ISFreeArcExtract v.4.0.rar).
It greatly simplifies the code and also makes it easier to add the ability to cancel the decompression.
#define ArcArchive "test.arc"
[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;
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
ArcTotalSize := Int1;
Log(Format('Total size of files to be extracted is %d MB', [ArcTotalSize]));
end
else
if What = 'write' then
begin
if ArcTotalSize > 0 then
begin
ArcProgressPage.SetProgress(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(ArcExtracted, ArcTotalSize);
end;
end;
if ArcCancel then Result := ArcCancelCode
else Result := 0;
end;
function FreeArcCmd(
Cmd1, Cmd2, Cmd3, Cmd4, Cmd5, Cmd6, Cmd7, Cmd8, Cmd9, Cmd10: string): Integer;
begin
ArcCancel := False;
try
Result :=
FreeArcExtract(
CreateCallback(#FreeArcCallback),
GetStringAsUtf8(Cmd1), GetStringAsUtf8(Cmd2), GetStringAsUtf8(Cmd3),
GetStringAsUtf8(Cmd4), GetStringAsUtf8(Cmd5), GetStringAsUtf8(Cmd6),
GetStringAsUtf8(Cmd7), GetStringAsUtf8(Cmd8), GetStringAsUtf8(Cmd9),
GetStringAsUtf8(Cmd10));
Log(Format('Arc command "%s" result %d', [Cmd1, Result]));
except
Result := -63;
end;
end;
function UnPackArchive(ArchivePath: string; DestPath: string): Integer;
begin
{ Find out length of files to be extracted - origsize }
Result := FreeArcCmd('l', '--', ArchivePath, '', '', '', '', '', '', '');
if Result = 0 then
begin
// Actually extract
Result :=
FreeArcCmd('x', '-o+', '-dp' + DestPath, '-w' + DestPath, '--', ArchivePath,
'', '', '', '');
end;
end;
procedure UnpackCancelButtonClick(Sender: TObject);
begin
ArcCancel := True;
end;
procedure ExtractArc;
var
ArcArchivePath: string;
UnpackResult: Integer;
PrevCancelButtonClick: TNotifyEvent;
Error: string;
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;
ArcArchivePath := ExpandConstant('{src}\{#ArcArchive}');
Log(Format('Arc extraction starting - %s', [ArcArchivePath]));
ArcExtracted := 0;
UnpackResult := UnPackArchive(ArcArchivePath, ExpandConstant('{app}'));
if UnpackResult <> 0 then
begin
if ArcCancel then
begin
Error := 'Extraction cancelled';
end
else
begin
Error := Format('Extraction failed with code %d', [UnpackResult]);
end;
MsgBox(Error, mbError, MB_OK);
end;
finally
Log('Arc extraction cleanup');
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.
The code extracts a separate .arc file. If you want to embed the archive to the installer, you can use
[Files]
Source: {#ArcArchive}; DestDir: "{tmp}"; Flags: nocompression deleteafterinstall
And extract the archive from the {tmp}:
ArcArchivePath := ExpandConstant('{tmp}\{#ArcArchive}');
Note that the unarc.dll from ISFreeArcExtract v.4.0.rar does not seem to support password protected archives. The version from ISFreeArcExtract v.4.2.rar does, but I'm not aware of trustworthy download link.
If you want to extract multiple archives, see Inno Setup - How to add multiple arc files to decompress?
All you need is this
http://fileforums.com/showthread.php?t=96619
This program have the latest compatibility with inno setup and also supports Password based file with multiple extensions.

How to have a custom click sound on button click in Inno Setup (Back + Next + Cancel)?

How to have button click sounds in Inno setup?
I mean a different for "Back", "Next" and "Cancel".
I know there might be some questions and also answers to them, but I'm new to this site and I need some help.
Thanks in advance...
You can use Inno Media Player to play sounds.
See question Playing sound during an Inno Setup install.
To trigger the sound on button clicks use a code like:
[Files]
Source: "next.mp3"; Flags: dontcopy
Source: "back.mp3"; Flags: dontcopy
Source: "cancel.mp3"; Flags: dontcopy
Source: "MediaPlayer.dll"; Flags: dontcopy
[Code]
type
TDirectShowEventProc = procedure(EventCode, Param1, Param2: Integer);
function DSInitializeAudioFile(
FileName: string; CallbackProc: TDirectShowEventProc): Boolean;
external 'DSInitializeAudioFile#files:mediaplayer.dll stdcall';
function DSPlayMediaFile: Boolean;
external 'DSPlayMediaFile#files:mediaplayer.dll stdcall';
function DSStopMediaPlay: Boolean;
external 'DSStopMediaPlay#files:mediaplayer.dll stdcall';
function GetTickCount: DWORD;
external 'GetTickCount#kernel32.dll stdcall';
procedure DeinitializeSetup;
begin
DSStopMediaPlay;
end;
var
PageChanged: DWORD;
procedure CurPageChanged(CurPageID: Integer);
begin
PageChanged := GetTickCount;
end;
procedure DirectShowEvent(EventCode, Param1, Param2: Integer);
begin
{ dummy }
end;
procedure PlaySound(FileName: string);
begin
DSStopMediaPlay;
ExtractTemporaryFile(FileName);
if DSInitializeAudioFile(ExpandConstant('{tmp}\') + FileName, #DirectShowEvent) then
begin
DSPlayMediaFile;
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
{ NextButtonClick is called even for skipped pages (like the Welcome page) and }
{ during silent installs. To detect that, we check if at least half }
{ second elapsed since the page was shown }
if GetTickCount - PageChanged > 500 then
begin
PlaySound('next.mp3');
end;
Result := True;
end;
function BackButtonClick(CurPageID: Integer): Boolean;
begin
PlaySound('back.mp3');
Result := True;
end;
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
PlaySound('cancel.mp3');
end;

TLabel or TNewStaticText - change .Font.Style on Focus like Cursor changes with .Cursor

Is it possible to change .Font.Style on Focus TLabel or TNewStaticText like it happens with cursor when we use .Cursor?
There is no built-in support to track mouse hovering in Inno Setup at this time. However, by intercepting window procedure of the controls you can track this by yourself. For the following example you will need the InnoCallback library:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
OutputDir=userdocs:Inno Setup Examples Output
[Files]
Source: "InnoCallback.dll"; DestDir: "{tmp}"; Flags: dontcopy
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
GWL_WNDPROC = -4;
WM_MOUSEMOVE = $0200;
type
WPARAM = UINT_PTR;
LPARAM = LongInt;
LRESULT = LongInt;
TWindowProc = function(hwnd: HWND; uMsg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT;
function SetCapture(hWnd: HWND): HWND;
external 'SetCapture#user32.dll stdcall';
function ReleaseCapture: BOOL;
external 'ReleaseCapture#user32.dll stdcall';
function GetMessagePos: DWORD;
external 'GetMessagePos#user32.dll stdcall';
function GetWindowRect(hWnd: HWND; out lpRect: TRect): BOOL;
external 'GetWindowRect#user32.dll stdcall';
function CallWindowProc(lpPrevWndFunc: LongInt; hWnd: HWND; Msg: UINT;
wParam: WPARAM; lParam: LPARAM): LRESULT;
external 'CallWindowProc{#AW}#user32.dll stdcall';
function SetWindowLong(hWnd: HWND; nIndex: Integer;
dwNewLong: LongInt): LongInt;
external 'SetWindowLong{#AW}#user32.dll stdcall';
function WrapWindowProc(Callback: TWindowProc; ParamCount: Integer): LongWord;
external 'wrapcallback#files:InnoCallback.dll stdcall';
type
TControlRec = record
Hovered: Boolean; // hovering state
WndProc: LongInt; // original window proc
Control: TWinControl; // control instance
end;
var
StaticText1: TNewStaticText;
StaticText2: TNewStaticText;
ControlList: array of TControlRec;
// helper function for finding control by handle
function GetControlRec(Handle: HWND): TControlRec;
var
I: Integer;
begin
for I := 0 to High(ControlList) do
if ControlList[I].Control.Handle = Handle then
begin
Result := ControlList[I];
Exit;
end;
end;
// function which attaches the intercepting window procedure to the control
// and creates and adds the control record to the control list
procedure AttachWndProc(Control: TWinControl; WindowProc: TWindowProc);
begin
SetArrayLength(ControlList, GetArrayLength(ControlList) + 1);
ControlList[High(ControlList)].Hovered := False;
ControlList[High(ControlList)].Control := Control;
ControlList[High(ControlList)].WndProc := SetWindowLong(Control.Handle,
GWL_WNDPROC, WrapWindowProc(WindowProc, 4));
end;
// function to restore windows procedures to all controls in the list
procedure RestoreWndProcs;
var
I: Integer;
begin
for I := 0 to High(ControlList) do
SetWindowLong(ControlList[I].Control.Handle, GWL_WNDPROC, ControlList[I].WndProc);
end;
// helper function to create a TPoint structure from the result of GetMessagePos
// function call
function MakePoint(Value: DWORD): TPoint;
begin
Result.X := Value and $FFFF;
Result.Y := Value shr 16;
end;
// helper function which substitutes PtInRect Windows API function which I wasn't
// able to import for some reason
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;
// interceptor window procedure
function StaticTextWndProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT;
var
P: TPoint;
R: TRect;
ControlRec: TControlRec;
begin
// get control record
ControlRec := GetControlRec(hwnd);
// if the cursor moves, then...
if uMsg = WM_MOUSEMOVE then
begin
// set mouse capture for this control to be notified by the WM_MOUSEMOVE even if
// we leave the control
SetCapture(ControlRec.Control.Handle);
// get the current cursor position and control rectangle (both screen relative)
P := MakePoint(GetMessagePos);
GetWindowRect(ControlRec.Control.Handle, R);
// check if the cursor is inside the control; if yes, then...
if PointInRect(R, P) then
begin
// if the hovering flag was not yet set, it means we just entered the control
// with the mouse, so let's change the style and remember the hovering state
if not ControlRec.Hovered then
begin
if ControlRec.Control is TNewStaticText then
TNewStaticText(ControlRec.Control).Font.Style := [fsBold];
ControlRec.Hovered := True;
end;
end
else
begin
// the cursor is not over the control, so let's release the mouse capture, set
// the style and remember the hovering state
ReleaseCapture;
if ControlRec.Control is TNewStaticText then
TNewStaticText(ControlRec.Control).Font.Style := [];
ControlRec.Hovered := False;
end;
end;
// call the original window procedure
Result := CallWindowProc(ControlRec.WndProc, hwnd, uMsg, wParam, lParam);
end;
procedure InitializeWizard;
begin
StaticText1 := TNewStaticText.Create(WizardForm);
StaticText1.Parent := WizardForm;
StaticText1.Left := 12;
StaticText1.Top := 336;
StaticText1.Caption := 'Hello';
StaticText2 := TNewStaticText.Create(WizardForm);
StaticText2.Parent := WizardForm;
StaticText2.Left := 43;
StaticText2.Top := 336;
StaticText2.Caption := 'world!';
AttachWndProc(StaticText1, #StaticTextWndProc);
AttachWndProc(StaticText2, #StaticTextWndProc);
end;
procedure DeinitializeSetup;
begin
RestoreWndProcs;
end;
for me the following command: High(ControlList) giving me the following error: Unknown identifier "High", I believe that High it's only available for Unicode Inno?? ( Correct me if I'm wrong :-). )
I made it work , by replacing the High(ControlList) with GetArrayLength(ControlList)-1.

InnoSetup: How to pass a two dimensional string array to a function

Innosetup is killing me. I am getting a RUNTIME 'Type Mismatch' error, where it is, to me, quite unexpected. I am using Inno-setup 5.5.3 (u)
(where the 'u' means the unicode version)
I am attempting to pass a two dimensional array into a method.
Here is my complete example.
[Setup]
AppName=EmptyProgram
AppVerName=EmptyProgram 1
UsePreviousAppDir=false
DefaultDirName={pf}\EmptyProgram
Uninstallable=false
OutputBaseFilename=HelloWorld
PrivilegesRequired=none
[Messages]
SetupAppTitle=My Title
[Code]
var
langMap : array[0..3] of array[0..1] of String;
function getMapVal(map : array of array[0..1] of String; key: String ) : String;
begin
Result:='not testing the body of the method';
end;
function InitializeSetup(): Boolean;
begin
MsgBox('Hello world.', mbInformation, MB_OK);
getMapVal(langMap, 'hello'); // this line here fails with type mismatch! Why?
Result := FALSE;
end;
This example would run, but for the invokation of the method:
getMapVal(langMap, 'hello');
It compiles, and therefore is happy with the declaration. But on invokation, mismatch error. What am I doing wrong?
First of all, you're not making a hash map, but a pure key value list. There's currently no way to make a real generics hash map in InnoSetup at this time. Anyway, your current code needs a complete refactor. I'd rather write it this way:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Code]
type
TKey = string;
TValue = string;
TKeyValue = record
Key: TKey;
Value: TValue;
end;
TKeyValueList = array of TKeyValue;
function TryGetValue(const KeyValueList: TKeyValueList; const Key: TKey;
var Value: TValue): Boolean;
var
I: Integer;
begin
Result := False;
for I := 0 to GetArrayLength(KeyValueList) - 1 do
if KeyValueList[I].Key = Key then
begin
Result := True;
Value := KeyValueList[I].Value;
Exit;
end;
end;
procedure InitializeWizard;
var
I: Integer;
Value: TValue;
KeyValueList: TKeyValueList;
begin
SetArrayLength(KeyValueList, 3);
for I := 0 to 2 do
begin
KeyValueList[I].Key := 'Key' + IntToStr(I);
KeyValueList[I].Value := 'Value' + IntToStr(I);
end;
if TryGetValue(KeyValueList, 'Key2', Value) then
MsgBox('Value: ' + Value, mbInformation, MB_OK);
end;

Resources