i have a text file and 10 StringLists, i want to open the txt files in the 10 StringLists, for example the text file has 1000 line, i want the first 100 line in StringList1 and the second 100 in StringLists2 and so on, my idea is to get text file lines count and divide it by 10 then copy each 100 in the 10 StringLists
var
i, x :integer;
U : TStrings;
DatFile ,ExePath:string;
begin
U := TStringList.Create;
ExePath := ExtractFilePath(Application.ExeName);
DatFile := ExePath + 'Test.txt';
U.LoadFromFile(DatFile);
x := U.Count Div 10;
Edit1.Text := IntToStr(x);
/// Stoped here
end;
how to continue this?
You can use an array to hold the Memo pointers, and then loop through the lines of the file, calculating which array index to add each line to, eg:
var
i, LinesPerMemo, LinesAdded: integer;
U : TStrings;
DatFile: string;
Memos: array[0..9] of TMemo;
CurMemo: TMemo;
begin
Memos[0] := Memo1;
Memos[1] := Memo2;
Memos[2] := Memo3;
Memos[3] := Memo4;
Memos[4] := Memo5;
Memos[5] := Memo6;
Memos[6] := Memo7;
Memos[7] := Memo8;
Memos[8] := Memo9;
Memos[9] := Memo10;
DatFile := ExtractFilePath(Application.ExeName) + 'Test.txt';
U := TStringList.Create;
try
U.LoadFromFile(DatFile);
LinesPerMemo := U.Count div 10;
if (U.Count mod 10) <> 0 then
Inc(LinesPerMemo);
Edit1.Text := IntToStr(LinesPerMemo);
J := 0;
CurMemo := Memos[J];
try
LinesAdded := 0;
for I := 0 to U.Count-1 do
begin
CurMemo.Lines.Add(U[I]);
Inc(LinesAdded);
if (LinesAdded = LinesPerMemo) and (J < 9) then
begin
CurMemo.Lines.EndUpdate;
Inc(J);
CurMemo := Memos[J];
CurMemo.Lines.BeginUpdate;
LinesAdded := 0;
end;
finally
CurMemo.Lines.EndUpdate;
end;
end;
finally
U.Free;
end;
end;
Alternatively, use a temp TStringList to collect the lines for each Memo:
var
i, LinesPerMemo: integer;
U, Lines : TStrings;
DatFile: string;
Memos: array[0..9] of TMemo;
begin
Memos[0] := Memo1;
Memos[1] := Memo2;
Memos[2] := Memo3;
Memos[3] := Memo4;
Memos[4] := Memo5;
Memos[5] := Memo6;
Memos[6] := Memo7;
Memos[7] := Memo8;
Memos[8] := Memo9;
Memos[9] := Memo10;
DatFile := ExtractFilePath(Application.ExeName) + 'Test.txt';
U := TStringList.Create;
try
U.LoadFromFile(DatFile);
LinesPerMemo := U.Count div 10;
if (U.Count mod 10) <> 0 then
Inc(LinesPerMemo);
Edit1.Text := IntToStr(LinesPerMemo);
Lines := TStringList.Create;
try
J := 0;
for I := 0 to U.Count-1 do
begin
Lines.Add(U[I]);
if (Lines.Count = LinesPerMemo) and (J < 9) then
begin
Memos[J].Lines.Assign(Lines);
Inc(J);
Lines.Clear;
end;
end;
Memos[J].Lines.Assign(Lines);
finally
Lines.Free;
end;
finally
U.Free;
end;
end;
To speed up, you can use Texfile and Tstringstream with creating Tmemo if needed.
type
TForm1 = class(TForm)
Button1: TButton;
ScrollBox1: TScrollBox;
procedure Button1Click(Sender: TObject);
private
{ Déclarations privées }
function getNewMemo(const aStream : Tstream) : TMemo;
public
{ Déclarations publiques }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
const nblines : Integer = 100;
var F : TextFile;
sLine, sfile : string;
cpt : Integer;
Memo : TMemo;
tmp : TStringStream;
begin
sfile := 'C:\TEMP\Test.txt';
tmp := TStringStream.Create;
AssignFile(F, sFile);
Reset(F);
try
LockWindowUpdate(ScrollBox1.Handle);
cpt := 0;
while not Eof(F) do begin
Readln(F, sLine);
Inc(cpt);
tmp.WriteString(sLine + #13);
if (cpt mod nbLines = 0) then begin
Memo := getNewMemo(tmp);
tmp.Clear;
end;
end;
if tmp.Size > 0 then begin
Memo := getNewMemo(tmp);
tmp.Clear;
end;
finally
CloseFile(F);
tmp.Free;
LockWindowUpdate(0);
end;
end;
function TForm1.getNewMemo(const aStream : Tstream): TMemo;
begin
Result := TMemo.Create(ScrollBox1);
Result.Parent := ScrollBox1;
Result.Top := High(integer);
Result.Align := alTop;
Result.Height := 150;
Result.ScrollBars := ssBoth;
if aStream <> nil then begin
aStream.Seek(0, soFromBeginning);
Result.Lines.LoadFromStream(aStream);
end;
end;
end.
Related
Let's say we have this Integer 1234567890, we want it converted to a string with a separator = 1.234.567.890, we could do Format('%n',[1234567890.0]); but it's very slow. I wrote a function to speed it up considerably (more than 2x faster). How could I improve it further, or can you come up with a faster routine?
function MyConvertDecToStrWithDot(Const n: UInt64): string;
Var a,b,x: Integer;
z,step: Integer;
l: SmallInt;
begin
Result := IntToStr(n);
if n < 1000 then Exit;
l := Length(Result);
a := l div 3;
b := l mod 3;
step := b+1;
z := 4;
if b <> 0 then begin
Insert('.',Result,step);
Inc(z,step);
end;
for x := 1 to (a-1) do begin
Insert('.',Result,z);
Inc(z,4);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
Var a: Integer;
s: string;
begin
PerfTimerInit;
for a := 1 to 1000000 do
s := MyConvertDecToStrWithDot(1234567890);
Memo1.lines.Add(PerfTimerStopMS.ToString);
caption := s;
end;
32-bit
Format: ~230ms
My function: ~79ms
64-bit
Format: ~440ms
My function: ~103ms
In my tests, the following is ever so slightly faster:
function ThousandsSepStringOf(Num: UInt64): string;
const
MaxChar = 30; // Probably, 26 is enough: 19 digits + 7 separators
var
Count: Integer;
Rem: UInt64;
Res: array[0..MaxChar] of Char;
WritePtr: PChar;
begin
WritePtr := #Res[MaxChar];
WritePtr^ := #0;
Count := 0;
while Num > 0 do
begin
DivMod(Num, 10, Num, Rem);
Dec(WritePtr);
WritePtr^ := Char(Byte(Rem) + Ord('0'));
Inc(Count);
if Count = 3 then
begin
Dec(WritePtr);
WritePtr^ := '.';
Count := 0;
end;
end;
if WritePtr^ = '.' then
Inc(WritePtr);
Count := MaxChar - ((NativeInt(WritePtr) - NativeInt(#Res)) shr 1);
SetLength(Result, Count);
Move(WritePtr^, PByte(Result)^, Count * SizeOf(Char));
end;
Tested with:
procedure TestHisCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('His code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := MyConvertDecToStrWithDot(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
procedure TestMyCode;
Var
a: Integer;
s: string;
SW: TStopwatch;
begin
Writeln('My code');
SW := TStopwatch.StartNew;
for a := 1 to KLoops do
s := ThousandsSepStringOf(1234567890);
Writeln(SW.ElapsedMilliseconds);
Writeln(s);
Writeln;
end;
and:
TestHisCode;
TestMyCode;
TestMyCode;
TestHisCode;
TestMyCode;
TestHisCode;
TestHisCode;
TestMyCode;
Haven't properly tested the performance of this, however it should be cross-platform and locale independent:
function Thousands(const ASource: string): string;
var
I, LLast: Integer;
begin
Result := ASource;
LLast := Length(Result);
I := LLast;
while I > 0 do
begin
if (LLast - I + 1) mod 3 = 0 then
begin
Insert(FormatSettings.ThousandSeparator, Result, I);
Dec(I, 2);
end
else
Dec(I);
end;
end;
Note: It obviously just works on integers
It's better to insert the separators directly while constructing the string instead of inserting separators later into the converted string because each insertion involves a lot of data movements and performance degradation. Besides avoid the division by 3 may improve performance a bit
This is what I get from my rusty Pascal after decades not using it
uses strutils;
function FormatNumber(n: integer): string;
var digit: integer;
count: integer;
isNegative: boolean;
begin
isNegative := (n < 0);
if isNegative then n := -n;
Result := '';
count := 3;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
Result := Result + '.';
count := 3;
end;
Result := Result + chr(ord('0') + digit);
dec(count);
end;
if isNegative then Result := Result + '-';
Result := reversestring(Result);
end;
See it in action: http://ideone.com/6O3e8w
It's also faster to just assign the characters directly instead of using concatenation operator/function like Victoria suggested. This is the improved version with only unsigned types
type string28 = string[28];
function FormatNumber(n: UInt64): string28;
var digit: integer;
length: integer;
count: integer;
c: char;
begin
count := 3;
length := 0;
while n <> 0 do begin
digit := n mod 10;
n := n div 10;
if count = 0 then begin
inc(length);
Result[length] := '.';
count := 3;
end;
inc(length);
Result[length] := chr(ord('0') + digit);
dec(count);
end;
for count := 1 to (length + 1) div 2 do begin
c := Result[count];
Result[count] := Result[length - count + 1];
Result[length - count + 1] := c;
end;
setlength(Result, length);
FormatNumber := Result;
end;
If the operation is done millions of times and is really a bottleneck after profiling, it's better to do in multiple threads along with SIMD
I want to use custom uninstall wizard pages as in my Inno Setup script shown in question Custom Uninstall page (not MsgBox).
The problem I have is that the Caption of the TSetupForm does not update, when a page changes.
I tried using the following code.
[Code]
const
ControlGap = 5;
procedure UpdateButtonsState(Form: TSetupForm);
var
Notebook: TNewNotebook;
BtnBack, BtnNext: TButton;
begin
Notebook := TNewNotebook(Form.FindComponent('Notebook'));
BtnBack := TButton(Form.FindComponent('BtnBack'));
BtnNext := TButton(Form.FindComponent('BtnNext'));
BtnBack.Enabled := (Notebook.ActivePage <> Notebook.Pages[0]);
if Notebook.ActivePage <> Notebook.Pages[Notebook.PageCount - 1] then
begin
BtnNext.Caption := SetupMessage(msgButtonNext)
BtnNext.ModalResult := mrNone;
end
else
begin
BtnNext.Caption := SetupMessage(msgButtonFinish);
BtnNext.ModalResult := mrYes;
end;
end;
procedure BtnPageChangeClick(Sender: TObject);
var
NextPage: TNewNotebookPage;
Notebook: TNewNotebook;
Form: TWinControl;
Button, BtnBack, BtnNext: TButton;
begin
Button := TButton(Sender);
Form := Button;
while not (Form is TSetupForm) do
Form := Form.Parent;
Notebook := TNewNotebook(Form.FindComponent('Notebook'));
BtnBack := TButton(Form.FindComponent('BtnBack'));
BtnNext := TButton(Form.FindComponent('BtnNext'));
if (Button = BtnBack) and (Notebook.ActivePage = Notebook.Pages[0]) then
NextPage := nil
else
if (Button = BtnNext) and (Notebook.ActivePage = Notebook.Pages[Notebook.PageCount - 1]) then
NextPage := nil
else
NextPage := Notebook.FindNextPage(Notebook.ActivePage, Button = BtnNext);
Notebook.ActivePage := NextPage;
UpdateButtonsState(TSetupForm(Form));
end;
function AddPage(NotebookForm: TSetupForm): TNewNotebookPage;
var
Notebook: TNewNotebook;
begin
Notebook := TNewNotebook(NotebookForm.FindComponent('Notebook'));
Result := TNewNotebookPage.Create(Notebook);
Result.Notebook:=Notebook;
Result.Parent:=Notebook;
Result.Align := alClient;
if Notebook.ActivePage = nil then
Notebook.ActivePage := Result;
UpdateButtonsState(NotebookForm);
end;
function CreateNotebookForm: TSetupForm;
var
Notebook: TNewNotebook;
NotebookPage: TNewNotebookPage;
Pan: TPanel;
TmpLabel: TLabel;
MaxWidth, i: Integer;
BtnBack, BtnNext, BtnCancel: TButton;
BtnLabelMsgIDs: Array Of TSetupMessageID;
begin
Result := CreateCustomForm;
Result.SetBounds(0, 0, UninstallProgressForm.Width, UninstallProgressForm.Height);
Result.Position := poOwnerFormCenter;
Notebook := TNewNotebook.Create(Result);
Notebook.Parent := Result;
Notebook.Name := 'Notebook';
Notebook.Align := alClient;
Pan := TPanel.Create(Result);
Pan.Parent := Notebook;
Pan.Caption := '';
Pan.Align := alBottom;
BtnNext := TNewButton.Create(Result);
with BtnNext do
begin
Parent := Pan;
Name := 'BtnNext';
Caption := SetupMessage(msgButtonNext);
OnClick := #BtnPageChangeClick;
ParentFont := True;
end;
BtnBack := TNewButton.Create(Result);
with BtnBack do
begin
Parent := Pan;
Caption := SetupMessage(msgButtonBack);
Name := 'BtnBack';
OnClick := #BtnPageChangeClick;
ParentFont := True;
end;
BtnCancel := TNewButton.Create(Result);
with BtnCancel do
begin
Parent := Pan;
Name := 'BtnCancel';
Caption := SetupMessage(msgButtonCancel);
ModalResult := mrCancel;
Cancel := True;
ParentFont := True;
end;
TmpLabel := TLabel.Create(Result);
with TmpLabel do
begin
Left := 0;
Top := 0;
Parent := Pan;
ParentFont := True;
Visible := False;
WordWrap := False;
Autosize := True;
end;
SetArrayLength(BtnLabelMsgIDs, 4);
BtnLabelMsgIDs[0] := msgButtonBack;
BtnLabelMsgIDs[1] := msgButtonNext;
BtnLabelMsgIDs[2] := msgButtonCancel;
BtnLabelMsgIDs[3] := msgButtonFinish;
MaxWidth := 0;
for i := Low(BtnLabelMsgIDs) to High(BtnLabelMsgIDs) do
begin
TmpLabel.Caption := SetupMessage(BtnLabelMsgIDs[i]) + 'WWW';
if MaxWidth < TmpLabel.Width then
MaxWidth := TmpLabel.Width;
end;
TmpLabel.Caption := 'Yy';
with BtnBack do
begin
Width := MaxWidth;
Height := TmpLabel.Height*2;
Left := Parent.ClientWidth - 3*(MaxWidth + ScaleX(ControlGap));
Top := (Parent.ClientHeight - Height) div 2;
end;
with BtnNext do
begin
Width := MaxWidth;
Height := TmpLabel.Height*2;
Left := Parent.ClientWidth - 2*(MaxWidth + ScaleX(ControlGap));
Top := (Parent.ClientHeight - Height) div 2;
end;
with BtnCancel do
begin
Width := MaxWidth;
Height := TmpLabel.Height*2;
Left := Parent.ClientWidth - 1*(MaxWidth + ScaleX(ControlGap));
Top := (Parent.ClientHeight - Height) div 2;
end;
end;
procedure InitializeUninstallProgressForm;
var
Form: TSetupForm;
i: Integer;
NotebookPage: TNewNotebookPage;
ModResult: Integer;
begin
Form := CreateNotebookForm;
for i := 1 to 4 do
begin
NotebookPage := AddPage(Form);
with NotebookPage do
begin
Color := clWindow;
with TLabel.Create(Form) do
begin
Parent := NotebookPage;
SetBounds(0, 0, 50, 30);
Autosize := true;
Font.Size := 14;
Caption := 'Label ' + IntToStr(i);
end;
Form.Caption := 'CAPTION - ' + IntToStr(i);
{<<<NEVER UPDATES AND KEEPS SHOWING "CAPTION - 4">>>>}
end;
end;
ModResult := Form.ShowModal;
if ModResult = mrYes then
MsgBox('Continuing uninstall', mbInformation, MB_OK)
else
begin
MsgBox('Cancelled', mbInformation, MB_OK);
Abort;
end;
end;
Please help me to find why Caption of the TSetupForm (Here it is declared as Form) never updates.
Thanks in advance.
Of course, it doesn't. You have to update the form caption as the page changes.
A good place for this is the end of the UpdateButtonsState function:
procedure UpdateButtonsState(Form: TSetupForm);
{ ... }
begin
{ ... }
if Notebook.ActivePage <> nil then
Form.Caption := 'CAPTION - ' + IntToStr(Notebook.ActivePage.PageIndex + 1);
end;
I am trying to use multithreading in Delphi XE.
The task is following I have to create 4 threads. Each thread draw colored circle in Paintbox at predefined area, For example FIRST thread draw only red circles in first quoter of the Paintbox, the SECOND thread draw only yellow circles in the second quoter, and so on.
I have defined following class
const
NumberOfIterations = 100000;
NumberOfTreads = 4;
TCalcThread = class(TThread)
private
FIdx: Integer;
FHits: Cardinal;
V: array of Integer;
xPaintBox1: TPaintBox;
protected
procedure Execute; override;
public
constructor Create(Idx: Integer; vPaintBox: TPaintBox);
property Hits: Cardinal read FHits;
end;
In main code I do the following:
procedure TForm11.Button1Click(Sender: TObject);
var
thrarr: array[0..NumberOfTreads - 1] of TCalcThread;
hndarr: array[0..NumberOfTreads - 1] of THandle;
i, a, t: Integer;
x, y: Integer;
begin
caption := '';
PaintBox1.Canvas.Brush.Color := clWhite;
PaintBox1.Canvas.fillrect(PaintBox1.Canvas.ClipRect);
for i := 0 to NumberOfTreads - 1 do
begin
thrarr[i] := TCalcThread.Create(i, PaintBox1);
hndarr[i] := thrarr[i].Handle;
end;
WaitForMultipleObjects(NumberOfTreads, #hndarr, True, INFINITE);
for i := 0 to NumberOfTreads - 1 do
thrarr[i].Free;
end;
The thread Create and Execute methods are defined as following:
constructor TCalcThread.Create(Idx: Integer; vPaintBox: TPaintBox);
begin
FIdx := Idx;
FHits := 0;
xPaintBox1 := vPaintBox;
case FIdx of
0: xPaintBox1.Canvas.Pen.Color := clRed;
1: xPaintBox1.Canvas.Pen.Color := clYellow;
2: xPaintBox1.Canvas.Pen.Color := clBlue;
3: xPaintBox1.Canvas.Pen.Color := clMoneyGreen;
end;
xPaintBox1.Canvas.Brush.Color := xPaintBox1.Canvas.Pen.Color;
inherited Create(False);
end;
procedure TCalcThread.Execute;
var
i, start, finish: Integer;
x, y: Integer;
begin
start := (NumberOfIterations div NumberOfTreads) * FIdx;
finish := start + (NumberOfIterations div NumberOfTreads) - 1;
for i := start to finish do
begin
case FIdx of
0: begin
x := Random(200) + 1;
end;
1: begin
x := Random(200) + 201;
end;
2: begin
x := Random(200) + 401;
end;
3: begin
x := Random(200) + 601;
end;
end;
y := Random((xPaintBox1.height )) + 1;
xPaintBox1.Canvas.Ellipse(X - 5, Y - 5, X + 5, Y + 5);
end;
end;
As a result I am getting a few circles in three areas with the same color, and a lot of circles in one area (the same color). What I am doing wrong?
What's the most efficient way to replace every third character of the same type in a string?
I have a string like this:
str := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9'
I want to replace every third #9 by #13#10, so that i get:
str1 := 'c1'#9'c2'#9'c3'#13#10'c4'#9'c5'#9'c6'#13#10'
I would do this in this way:
i:=0;
newStr:='';
lastPos := Pos(str,#9);
while lastPos > 0 do begin
if i mod 3 = 2 then begin
newStr := newStr + Copy(str,1,lastPos-1) + #13#10;
end else begin
newStr := newStr + Copy(str,1,lastPos);
end;
str := Copy(str,lastPos+1,MaxInt);
i := i + 1;
lastPos := Pos(str,#9);
end;
newStr := Copy(str,1,MaxInt);
But thats a lot of copying. Is there a string manipulation function to do this?
I think the problem as stated doesn't match the code you provided. Is every third character a #9? If so, do you want to change every third appearance of #9 for #13#10?
If so, I would do it this way:
function test(str: string): string;
var
i, c, l: integer;
begin
l := Length(str);
SetLength(Result, l + l div 9);
c := 1;
for i := 1 to l do
begin
if (i mod 9 = 0) and (i > 0) then
begin
Result[c] := #13;
Inc(c);
Result[c] := #10;
end
else
Result[c] := str[i];
Inc(c);
end;
end;
I actually have no idea if this function performs well. But given that the constraints aren't clear, I guess so.
If the position of the #9 character is unknown then this solution won't work at all.
Edit: as David points out, this is not nearly equivalent to the original code posted. This seems to work, but it requires two passes on the original string. The thing is, to know if its more efficient or not we need to know more about the input and context.
function OccurrencesOfChar(const S: string; const C: char): integer;
var
i: integer;
begin
result := 0;
for i := 1 to Length(S) do
if S[i] = C then
inc(result);
end;
function Test(str: string): string;
var
len, n, C, i: integer;
begin
C := 1;
len := Length(str);
n := OccurrencesOfChar(str, #9);
SetLength(result, len + n div 3);
n := 1;
for i := 1 to len do
begin
if str[i] = #9 then
begin
if n mod 3 = 0 then
begin
result[C] := #13;
inc(C);
result[C] := #10;
end
else
result[C] := #9;
Inc(n);
end
else
result[C] := str[i];
inc(C);
end;
end;
I expect this question will be closed, but just for fun, that would be my proposal.
Function Replace(const Instr:String;Re:Char;const ReWith:String):String;
var
i,o,cnt,l:Integer;
begin
cnt:=0;
o:=0;
SetLength(Result,Length(Instr)*Length(ReWith));// just for security
for I := 1 to Length(Instr) do
begin
if Instr[i]=Re then inc(cnt);
if cnt=3 then
begin
for l := 1 to Length(ReWith) do
begin
inc(o);
Result[o] := ReWith[l];
end;
cnt := 0;
end
else
begin
inc(o);
Result[o] := Instr[i];
end;
end;
SetLength(Result,o);
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
Edit2.Text := Replace(Edit1.Text,'A','xxx')
end;
I would probably do something like this (coded in the browser). It only needs one string resize and should have less movement of data around. I exit when I have made the last replacement or if it didn't need any:
procedure ReplaceXChar(var aStringToReplace: string; const aIteration:
Integer; const aChar: Char; const aReplacement: string);
var
replaceCount: Integer;
cntr: Integer;
outputCntr: Integer;
lengthToReplace: Integer;
begin
// Find the number to replace
replaceCount := 0;
for cntr := 1 to Length(aStringToReplace) do
begin
if aStringToReplace[cntr] = aChar then
Inc(replaceCount);
end;
if replaceCount >= aIteration then
begin
// Now replace them
lengthToReplace := Length(aReplacement);
cntr := Length(aStringToReplace);
SetLength(aStringToReplace, cntr +
(replaceCount div aIteration) * (lengthToReplace - 1));
outputCntr := Length(aStringToReplace);
repeat
if aStringToReplace[cntr] = aChar then
begin
if (replaceCount mod aIteration) = 0 then
begin
Dec(outputCntr, lengthToReplace);
Move(aReplacement[1], aStringToReplace[outputCntr+1],
lengthToReplace * SizeOf(Char));
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(replaceCount);
end
else
begin
aStringToReplace[outputCntr] := aStringToReplace[cntr];
Dec(outputCntr);
end;
Dec(cntr);
until replaceCount = 0;
end;
end;
Usage would be like this:
var
myString: String;
begin
myString := 'c1'#9'c2'#9'c3'#9'c4'#9'c5'#9'c6'#9;
ReplaceXChar(myString, 3, #9, #13#10);
ShowMessage(myString);
end;
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.