How to save to an INI file delphi - file

I have buttons which when clicked store their caption in a TStringList
ChairList : TStringList;
procedure TForm1.FormCreate(Sender: TObject);
begin
ChairList := TStringList.Create;
end;
An example of one button is:
procedure TForm1.Table17Click(Sender: TObject);
begin
Label1.Visible := false;
if (BottomPanel.Visible = false) then
begin
Label1.Visible := true;
LastClicked := 'Table 17';
ChairList.Add(LastClicked);
But when I read the file I get no result back. I tested it by using a ShowMessage just to see if anything would be read and I just get a blank ShowMessage
The following code is the save procedure:
Procedure TForm1.SaveToFile(Const Filename : String);
Var
INI : TMemIniFile;
Procedure SaveReserve();
var
section : String;
Begin
Section := 'Table';
ini.writeString(Section, 'LastClickedID', LastClicked);
End;
begin
Ini := Tmeminifile.Create(filename);
ini.Clear;
try
SaveReserve();
Ini.UpdateFile;
finally
Ini.Free;
end;
end;
Then the next Procedure is the load file:
Procedure TForm1.LoadFile(const Filename : String);
var
INI : TMemInifile;
Sections : TStringList;
i : Integer;
LastClickedID : String;
Procedure LoadChair(Const Section: String);
Begin
LastClickedID := INI.ReadString(Section, 'LastClickedID', LastClicked)
End;
Begin
ChairList.Clear;
INI := TMEMINIFILE.Create(Filename);
Try
Sections := TStringList.Create;
Try
Ini.ReadSections(Sections);
for i := 0 to (Sections.Count - 1) do
Begin
if startstext('LastClickedID', Sections[I]) then
begin
LastClickedID := (copy(Sections[I], 12, MaxInt));
end;
End;
Finally
Sections.Free;
End;
Finally
INI.Free;
end;
ShowTable(LastClickedID);
end;
ShowTable(LastClickedID); is just the part where i test it
How can i make it so it saves the caption and then when i load it up it shows any table caption saved?
I only need it to save the caption of an object selected. When the user selects a button it adds the caption to the stringlist which is then read to the ini file

try this code!
Procedure for write ini:
procedure write_ini;
var
ini:TIniFile;
begin
ini:=TIniFile.Create('MainForm.ini');
ini.WriteInteger('FORM', 'Top', MainForm.Top);
ini.WriteInteger('FORM', 'Left', MainForm.Left);
ini.WriteInteger('FORM', 'Width', MainForm.Width);
ini.WriteInteger('FORM', 'Height', MainForm.Height);
ini.WriteString('USER', 'Name', MainForm.userName.Text);
ini.WriteInteger('USER','Pol', MainForm.Combo.ItemIndex);
ini.Free;
end;
Procedure for read ini:
procedure read_ini;
var
ini:TIniFile;
begin
ini:=TiniFile.Create('MainForm.ini');
MainForm.Top:=ini.ReadInteger('FORM', 'Top', 0);
MainForm.Left:=ini.ReadInteger('FORM', 'Left', 0);
MainForm.Width:=ini.ReadInteger('FORM', 'Width', 226);
MainForm.Height:=ini.ReadInteger('FORM', 'Height', 123);
MainForm.userName.Text:=ini.ReadString('USER', 'Name', 'Anonim');
MainForm.Combo.ItemIndex:=ini.ReadInteger('USER', 'Pol', 1);
ini.Free;
end;

Your code fails to load the value back because you are trying to read it from the wrong area of the INI file.
You are writing the value to the 'LastClickedID' entry of the 'Table' section, which produces this INI data:
[Table]
LastClickedID=Value
But then you are trying to read the value from the 'LastClickedID' entry of any section that begins with the 'LastClickedID' prefix, not from the 'Table' section that you previously wrote to. That would only work if the INI data looked more like this instead:
[LastClickedID]
LastClickedID=Value
Since that section does not exist in the INI, that is why your LastClickedID variable is always blank (if LastClicked is also blank to begin with).
You don't need to use Ini.ReadSections() in this example. Just use Ini.ReadString() by itself with the proper 'Table' section name, eg:
procedure TForm1.LoadFile(const Filename : String);
var
Ini : TMemIniFile;
LastClickedID: string;
procedure LoadReserve;
var
section : String;
begin
Section := 'Table';
LastClickedID := Ini.ReadString(Section, 'LastClickedID', LastClicked);
end;
begin
Ini := TMemIniFile.Create(Filename);
try
LoadReserve;
finally
Ini.Free;
end;
ShowTable(LastClickedID);
end;
In general, you should be mirroring the opposite of whatever your SaveToFile() code is doing.

I advise you to read this documentation and example to use TMemIniFile and TIniFile. It should be helpful...
Here is my code :
Procedure TForm1.SaveToFile(Const Filename : String);
Var
Ini : TIniFile;
begin
Ini := TIniFile.Create(filename);
try
Ini.WriteString('Table', 'LastClickedID', LastClicked);
finally
Ini.Free;
end;
end;
Procedure TForm1.LoadFile(const Filename : String);
var
Ini : TInifile;
LastClickedID : String;
begin
Ini := TIniFile.Create(Filename);
try
LastClickedID := Ini.ReadString('Table', 'LastClickedID', '');
finally
Ini.Free;
end;
ShowTable(LastClickedID);
end;

You can create a general ini file utility object, which can read/write under the predefined key, the properties collected by a dictionary object. This is a flexible, reusable solution.
You can collect the clicked control captions into the dictionary and save the list at the end of your process. You should use a dictionary because a key/value pair needed for an ini file operation.
uses
Generics.Collections
, System.IniFiles
;
TIniFileUtility = class
public
class read( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string> );
class write( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string> );
end;
class procedure TIniFileUtility.write( ini_ : TIniFile; keyName_ : string; props_ : TDictionary<string,string>);
var
i : integer;
key, value : string;
begin
//... some input parameter checking
for key in props_.keys do
begin
value := props_.Items[key];
ini_.writeString( keyName_, key, value );
end;
end;
procedure caller(Sender: TObject);
var
ini : TIniFile;
props : TDictionary<string,string>;
begin
ini := TIniFile.create( 'c:\temp\defaults.ini' );
try
props := TDictionary<string,string>.Create;
try
props.Add( 'key1', 'stringValue' );
props.Add( 'key2', intToStr( 2 ) );
TIniFileUtility.write( ini, '\x\y\z\', props );
finally
props.Free;
props := NIL;
end;
finally
ini.Free;
ini := NIL;
end;
end;

Related

Create an Array from Json Object

I have the following JSON structure, calling it from web:
[
{
"Code":"31212",
"Desc":"JOHN",
"DOYDESC":"MADRID",
"Street":"Str41",
"StreetNo":"86"
},
{
"Code":"30214",
"Desc":"GEORGE",
"DOYDESC":"NEW YORK",
"Street":"Str3",
"StreetNo":null
},
{
"Code":"09215",
"Desc":"MARY",
"DOYDESC":"PARIS",
"Street":"Str3",
"StreetNo":"22"
},
{
"Code":"10217",
"Desc":"MEGAN",
"DOYDESC":"ROME",
"Street":"Str4",
"StreetNo":null
}
]
I want the user to give a number in Edit.Text, and then search the values of Code to find if the given value exists.
Is there a way to create an array named Code with the values from the JSON?
For example, to create something like this:
Code = ["31212","30214","09215","10217"]
and then make the search? Or, is there another way to find a specific value of Code from the JSON? I tried this so far:
procedure Tlog_form.Button2Click(Sender: TObject);
var
jso : TJsonObject;
jsv,jsval : TJsonValue;
jsa,jsarr : TJsonArray;
data,str : string;
i,j,user : integer;
jsp : TJsonPair;
arr : array of string;
begin
try
data := GetURLAsString('http://...');
except
on E: exception do
end;
try
jsv := TJSONObject.ParseJSONValue(data);
try
jsa := jsv as TJSONArray;
try
for I := 0 to jsa.Size - 1 do
begin
jso := jsa.Get(i) as TJsonObject;
if jso.ToString = 'Code' then
begin
str := jso.GetValue('Code').ToString;
if Edit1.Text = str then
begin
ShowMessage('found it');
end;
end;
end;
finally
end;
finally
jsv.Free;
end;
except
on E: exception do
end;
end;
When I debug, it doesn't get any error. but it still doesn't work.
You are converting each TJSONObject in the array to a string, and then comparing that to your TEdit text. Which will obviously not match. You need to compare only the Code field of each TJSONObject, which you would be doing if you remove the if jso.ToString = 'Code' then check completely, eg:
procedure Tlog_form.Button2Click(Sender: TObject);
var
jsv : TJSONValue;
jsa : TJSONArray;
jso : TJSONObject;
data, str, code : string;
I : integer;
begin
str := Edit1.Text;
try
data := GetURLAsString('http://...');
jsv := TJSONObject.ParseJSONValue(data);
if jsv <> nil then
try
jsa := jsv as TJSONArray;
for I := 0 to jsa.Size - 1 do
begin
jso := jsa.Get(I) as TJSONObject;
code := jso.GetValue('Code').ToString;
if str = code then
begin
ShowMessage('found it');
end;
end;
finally
jsv.Free;
end;
except
end;
end;
I'm not sure I understand you very well, but if you try TALJsonDocument (https://github.com/Zeus64/alcinoe), you have this function:
Function ALFindJsonNodeByTextChildNodeValue(const JsonNode:TalJsonNode;
Const ChildNodeName: AnsiString;
Const ChildNodeValue : AnsiString;
Const Recurse: Boolean = False): TALJsonNode;

Delphi TRichEdit to Array and local storage

I'm looking for informations on how to put TRichEdit into an Array and save it to Local file (ex: File.dat).
The goal is to store a number of text, with a description, and the 'name' of it.
I think I have to start with:
type
TMessage = record
Name : string;
Desc : string;
Text : TMemoryStream;
end;
var ARListMessages: array[1..50] of TMessage
And add data with something like:
richedit.Lines.SaveToStream( ARListMessages[i].Text );
How to create the Array, and make manipulations on it (Add, remove
...) with the 'name'?
How can I save it (Array), and load it easily from local storage ? (Ex:
File.dat)
I've found some informations here, without beeing able to make something working.
Thanks for your time.
[EDIT 18/09/2017]
I'm Still looking to find a solution, and try to find a way to save my informations to a local file.
My actual code to test is :
var
MessageArray : array of TMessage;
// // // //
SetLength(MessageArray, 1);
MessageArray[0].Name := 'Hey You';
MessageArray[0].Desc := 'Im here and will stay here, just in case';
MessageArray[0].Text := TMemoryStream.Create;
MessageArray[0].Text.Position := 0;
RichEdit1.plaintext := false;
RichEdit1.Lines.SaveToStream( MessageArray[0].Text );
So, looking to save MessageArray, but haven't find how to do that yet.
I've take a look on SuperObjet, but can't find how to deal with it.
Omxl was looking Great and easy, but free trial ... :(
I've been able to have an answer.
I share it here if someone need the solution.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.Generics.Collections, Classes, System.Zip;
type
TPersonne = class
strict private
FNom : string;
FPrenom: string;
public
property Nom: string read FNom write FNom;
property Prenom: string read FPrenom write FPrenom;
end;
TFamille = class(TDictionary<string, TPersonne>)
private
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
public
procedure LoadFromFile(const AFileName: string);
procedure SaveToFile(const AFileName: string);
end;
procedure TFamille.SaveToStream(stream: TStream);
var
i: Integer;
Personne: TPersonne;
Pair: System.Generics.Collections.TPair<string, TPersonne>;
Writer: TWriter;
begin
Writer := TWriter.Create(stream, 4096);
try
Writer.WriteListBegin;
for Pair in Self do
begin
Writer.WriteString(Pair.Key);
Writer.WriteListBegin;
Personne := Pair.Value;
Writer.WriteString(Personne.Nom);
Writer.WriteString(Personne.Prenom);
Writer.WriteListEnd;
end;
Writer.WriteListEnd;
finally
Writer.Free;
end;
end;
procedure TFamille.LoadFromStream(stream: TStream);
var
Personne: TPersonne;
Reader: TReader;
sKey: string;
begin
Clear;
Reader := TReader.Create(stream, 1024);
try
Reader.ReadListBegin;
while not Reader.EndOfList do
begin
sKey := Reader.ReadString;
Personne := TPersonne.Create;
Reader.ReadListBegin;
while not Reader.EndOfList do
begin
Personne.Nom := Reader.ReadString;
Personne.Prenom := Reader.ReadString;
end;
Reader.ReadListEnd;
Add(sKey, Personne);
end;
Reader.ReadListEnd;
finally
Reader.Free;
end;
end;
procedure TFamille.LoadFromFile(const AFileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(AFileName, fmOpenRead);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
procedure TFamille.SaveToFile(const AFileName: string);
var
stream: TStream;
begin
stream := TFileStream.Create(AFileName, fmCreate);
try
SaveToStream(stream);
finally
stream.Free;
end;
end;
var
Famille: TFamille;
Personne: TPersonne;
begin
try
Famille := TFamille.Create;
try
Personne := TPersonne.Create;
Personne.Nom := 'Julie';
Personne.Prenom := 'Albertine';
Famille.Add('1', Personne);
Famille.SaveToFile('D:\famille.txt');
finally
FreeAndNil(Famille);
end;
Famille := TFamille.Create;
try
Famille.LoadFromFile('D:\famille.txt');
if Famille.Count > 0 then
Writeln(Famille['1'].Nom + ' ' + Famille['1'].Prenom);
finally
FreeAndNil(Famille);
end;
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Thanks all for your Help !
How to create the Array, and make manipulations on it (Add, remove
...) with the 'name'?
From your first code the array is already created. No further work is needed. And even if you use dynamic arrays you still don't have to do any thing just declaring it will suffice.
If you are asking "How to create the array ?" as in "How to create such a list, so I could add, remove with the 'name' field" then I would suggest TDictionary (instead of the city names use your field 'name' as a key) to do the manipulations and then when saving use this
How can I save it (Array), and load it easily from local storage ?
(Ex: File.dat)
You can't directly just like array.savetofile;.You need to use one of file handling methods like TFileStream to store the array.
Note: in your edit the memory stream will cause you only trouble because each time you create it you will need to free it after its use instead use these functions to extract formatted text and change the field Text : TMemoryStream; to Text : string;
RichTextToStr: Convert the rich format text to a string.
function RichTextToStr(RichEdit:TRichEdit):string;
var
SS : TStringStream;
begin
SS := TStringStream.Create('');
try
RichEdit.Lines.SaveToStream(SS);
Result := SS.DataString;
finally
SS.Free;
end;
end;
loadToRichedit: Load the formatted text to the RichEdit again.
procedure loadToRichedit(Const St:string;RichEdit:TRichEdit);
var
SS : TStringStream;
begin
SS := TStringStream.Create(St);
try
RichEdit.Lines.LoadFromStream(SS);
finally
SS.Free;
end;
end;

Delphi Saving/Loading Dynamic Arrays Failed

I think this will look like 'do my homework' kind of a question, but I'm still at the 'copy code, use it and try to understand it' phase, and this is the most active thing I know of for posting questions of this theme.
I have a record:
type
Card = record
Name: string;
Up,Right,Left,Down,Mark: Single;
IDNumber: Integer;
end;
And array of that record:
var
ArrayCard: array of Card;
And I wanted to know how can a dynamic array of this kind be stored/loaded to/from a file.
Tried using this piece of code: http://www.pascalgamedevelopment.com/showthread.php?6319-save-load-a-dynamic-array
like this:
Procedure TMainFrom.WriteMyData;
Var
FS : TFileStream;
I,iSize : Integer;
TmpPath: string;
Begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
FS := TFileStream.Create(TmpPath, fmOpenWrite);
iSize:= Length(ArrayCard);
FS.WriteBuffer(iSize,SizeOf(iSize));
For I := 0 To iSize - 1 Do
FS.WriteBuffer(ArrayCard[I],SizeOf(Card));
FS.Free;
End;
An it seems to work so far, but then I try to load it like this:
Procedure TMainFrom.ReadMyData;
Var
FS : TFileStream;
I,iSize : Integer;
TmpPath: string;
TempCard : Card;
Begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.dat');
FS := TFileStream.Create(TmpPath, fmOpenRead);
FS.ReadBuffer(iSize,SizeOf(iSize));
SetLength(ArrayCard,iSize);
For I := 0 To iSize - 1 Do
Begin
FS.ReadBuffer(TempCard,SizeOf(Card));
ArrayCard[I] := TempCard; //It Breaks Here...The Watch List: TempCard Inaccessible value
End;
FS.Free;
End;
And I get a Exception EAccessViolation in module...
Then I also tried something like this: delphi Save and Load dynamic array
It loads the array with the correct amount of items, but they are all empty or blank:
procedure TMainFrom.SaveCardsToFile;
var
Stream: TStream;
L: Integer;
TmpPath: string;
ICount: Integer;
strm: TMemoryStream;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmCreate);
L:= Length(ArrayCard);
Stream.WriteBuffer(L, SizeOf(L));
Stream.WriteBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
Stream.Free;
end;
procedure TMainFrom.LoadCardsFromFile;
var
Stream: TStream;
L: LongWord;
TmpPath: string;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmOpenRead);
Stream.ReadBuffer(L, SizeOf(L));
SetLength(ArrayCard, L);
Stream.ReadBuffer(Pointer(ArrayCard)^, L * SizeOf(Card));
Stream.Free;
end;
Do not use buffer operations with records which contains "normal" strings. Instead of shortstring, string is only pointer to string content. So, your code saves and loads only pointer to string content, not string. You can get Access Violation if loaded value points to unreachable memory.
Change your code to separately save and load variables in record, like this:
type
TmyRec = record
str: string;
i: integer;
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
{ myRec }
procedure TmyRec.LoadFromStream(Stream: TStream);
var
strLen: integer;
strBuf: TBytes;
begin
Stream.Read(strLen, SizeOf(Integer));
SetLength(strBuf, strLen);
Stream.Read(strBuf, strLen);
str:=TEncoding.UTF8.GetString(strBuf);
Stream.Read(i, SizeOf(Integer));
end;
procedure TmyRec.SaveToStream(Stream: TStream);
var
strBuf: TBytes;
strLen: integer;
begin
// direct set encoding type helps to avoid problems with different platforms.
// for example, Windows uses UCS2, Android and iOS - UTF8 encoding
strBuf:=TEncoding.UTF8.GetBytes(str);
strLen:=Length(strBuf);
Stream.Write(strLen, SizeOf(Integer));
Stream.Write(strBuf, strLen);
Stream.Write(i, SizeOf(Integer));
end;
Update:
do you read about generics? Instead of dynamic array, you can use TList and load/save records in it:
type
TmyRecList = class(TList<TmyRec>)
public
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
{ TmyRecList }
procedure TmyRecList.LoadFromStream(Stream: TStream);
var
Len: integer;
i: Integer;
rec: TmyRec;
begin
Clear;
Stream.Read(Len, SizeOf(integer));
for i := 0 to Len-1 do
begin
Rec.LoadFromStream(Stream);
Add(rec);
end;
end;
procedure TmyRecList.SaveToStream(Stream: TStream);
var
i: Integer;
begin
Stream.Write(Count, SizeOf(Integer));
for i := 0 to Count-1 do
Items[i].SaveToStream(Stream);
end;
procedure THeaderFooterForm.FormCreate(Sender: TObject);
var
Stream: TStream;
rec: TmyRec;
recList: TmyRecList;
begin
Stream:=TMemoryStream.Create;
try
recList:=TmyRecList.Create;
try
rec.str:='sample text';
rec.i:=123;
recList.Add(rec);
rec.str:='another text';
rec.i:=234;
recList.Add(rec);
recList.SaveToStream(Stream);
Stream.Seek(0, soBeginning);
recList.LoadFromStream(Stream);
ShowMessage('this is str value in second record: ' + recList[1].str);
finally
recList.Free;
end;
finally
Stream.Free;
end;
With some help, I have managed to remake my code to work properly.
Firstly I needed to make the string something like string[20] but that couldn't compile for android so I modified my record to use array of char like this:
type
EventString= array [0..20] of Char;
Card = record
Name: EventString; //string[20]
Up,Right,Left,Down,Mark: Single;
IDNumber: Integer;
end;
Then I modified my Saving/Loading procedures to use TFileStream instead of TStream and a for loop:
procedure TMainFrom.SaveCardsToFile;
var
Stream: TFileStream;
L: Integer;
TmpPath: string;
n: Integer;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmCreate);
L:= Length(ArrayCard)-1;
for n:=0 to L do
begin
Stream.WriteBuffer(ArrayCard[n], SizeOf(Card)); //Saving
end;
Stream.Free;
end;
procedure TMainFrom.LoadCardsFromFile;
var
Stream: TFileStream;
L: LongWord;
TmpPath: string;
n: Integer;
begin
TmpPath:= TPath.Combine(TPath.GetDocumentsPath, 'Cards.bin');
Stream:= TFileStream.Create(TmpPath, fmOpenRead);
SetLength(ArrayCard, Round(Stream.Size/SizeOf(Card)));
L:= Length(ArrayCard)-1;
for n:=0 to L do
begin
Stream.ReadBuffer(ArrayCard[n], SizeOf(Card)); //Loading
ListCard.Items.Add(ArrayCard[n].Name); //Just adds card names to a listbox
end;
Stream.Free;
end;
And now it works just fine.

TurboPowern OnGuard create license file with machine modifier

I use TurboPower onGuard for licensing and there is an option to specify and generate the machine specific keys for license validation. With following code I create machine modifier based on machineID
const
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.FormCreate(Sender: TObject);
var
InfoSet : TEsMachineInfoSet;
MachineID : Longint;
begin
{ initialize the machine information set }
InfoSet := [midUser, midSystem, midNetwork, midDrives];
{ create the machine ID and display in hex }
try
MachineID := CreateMachineID(InfoSet);
Edit1.Text := '$' + BufferToHex(MachineID, SizeOf(MachineID));
except on E:Exception do
ShowMessage(E.Message);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
modifier :LongInt;
key :TKey;
releasecode,unlockcode :TCode;
inexpirydate: TdateTime;
begin
modifier := GenerateMachineModifierPrim ;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
//HexToBuffer(Ckey, key, SizeOf(TKey));
InitRegCode(Ckey, edit1.Text, inExpiryDate, releaseCode);
Edit2.text := BufferToHex(unlockCode, SizeOf(releaseCode));
end;
On the other hand to validate serial number and relase code I use the following code on client side to validate application, I place SerialNumberCode component to implement that.
Ckey :TKey = ($56,$1B,$B0,$48,$2D,$AF,$F4,$E5,$30,$6C,$E5,$3B,$A7,$8E,$1A,$14);
procedure TForm1.OgSerialNumberCode1GetKey(Sender: TObject; var Key: TKey);
begin
Key := Ckey;
end;
procedure TForm1.OgSerialNumberCode1GetModifier(Sender: TObject;
var Value: LongInt);
begin
Value := GenerateMachineModifierPrim;
end;
function TForm1.GetSNData(var S: string): integer;
var
TC : TCode;
SNC : string;
modifier :LOngInt;
begin
try
modifier := GenerateMachineModifierPrim;
ApplyModifierToKeyPrim(Modifier, CKey, SizeOf(CKey));
IniSNVal := StrToInt(InputBox('Serial number', 'Enter serial Value ', '12345678'));
SNC := (InputBox('release code', 'Enter code ', ''));
{Check that Release Code was entered correctly}
HexToBuffer(SNC, TC, SizeOf(TCode));
if not (IsSerialNumberCodeValid(CKey, TC)) then begin
S := 'Release code not entered correctly';
Result := mrCancel;
end else begin
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
IniFile.WriteInteger('Codes', 'SN', IniSNVal);
IniFile.WriteString('Codes', 'SNCode', SNC);
finally
IniFile.Free;
end;
end;
finally
end;
end;
procedure TForm1.OgSerialNumberCode1GetCode(Sender: TObject; var Code: TCode);
var
S1 : string;
L : integer;
begin
if not (FileExists(TheDir + 'test.INI')) then
Exit;
{open Ini File}
IniFile := TIniFile.Create(TheDir + 'test.INI');
try
{try to read release code}
S1 := IniFile.ReadString('Codes', 'SNCode', '');
IniSNVal := IniFile.ReadInteger('Codes', 'SN', 0);
{convert retrieved string to a code}
HexToBuffer(S1, Code, SizeOf(Code));
finally
IniFile.Free;
end;
end;
procedure TForm1.OgSerialNumberCode1Checked(Sender: TObject; Status: TCodeStatus);
var
S,
C1,
C2 : string;
TC : TCode;
LI : longint;
begin
case Status of
ogValidCode : begin
{check if retrieved Serial Number matches Code}
LI := OgSerialNumberCode1.GetValue;
if (LI <> IniSNVal) then begin
Status := ogInvalidCode;
S := 'The serial number has been changed';
end else begin
ShowMessage('Serial #: ' + IntToStr(IniSNVal));
Exit;
end;
end;
ogInvalidCode : begin
{if INI file doesn't exist, presume this is first run}
if not (FileExists(TheDir + 'test.INI')) then
begin
if not (GetSNData(S) = mrCancel) then begin
{Check the SN/ReleaseCode}
OgSerialNumberCode1.CheckCode(True);
{must Exit since line above began a recursive call}
Exit;
end;
end else
S := 'Invalid Code';
end;
ogCodeExpired : S := 'Evaluation period expired';
end;
ShowMessage(S);
Application.Terminate;
end;
But at the client side machine never accepts the validation and throws Release code not entered correctly. Probably I don't understand usage of machineID exmaple in onguard folder. Please also note that the key for client side and generation for relaease code is same so there shouldn't be any mismatch.

How to use DefineProperties in a custom Class Object for Arrays - Delphi

I'm trying to create my own class object and use it to store various data types for my application, this all works fine when using Published Properties, I can stream these to disk and back with no problems. But I need to stream some Arrays of both integer and strings data types as well.
I understand that Arrays, amongst other data types can't be published properties because Delphi doesn't know how to stream them, I was led to believe you need to use DefineProperties to accomplish this, I've created a test Array of String as a Public property, I can read and write to it just fine, however I need to stream it to disk so i can save it for future use.
The only thing i can find that touches on this subject is here:
Array of a custom class as a property
I've attempted to copy this code and manipulate it to archive what I need but I cannot get it to save, I'm seemingly missing something obvious, my test code I'm using is below, I get no errors with this code, published properties stream to disk ok but my private array does not. Any help would be greatly appreciated.
Thanks.
unit UnitDataSet;
//------------------------------------------------------------------------------
interface
uses System.Classes;
{$M+}
//------------------------------------------------------------------------------
type
TDataStrings = Array [1..50] of String;
TDataSet = class(TComponent)
protected
procedure DefineProperties(Filer: TFiler); override;
procedure ReadArray(Reader: TReader);
procedure WriteArray(Writer: TWriter);
private
FArrayToSave : TDataStrings;
FPStr : String;
function GetItem(I: Integer): String;
procedure SetItem(I: Integer; Value: string);
public
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
procedure LoadFromStream(const Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SaveToStream(const Stream: TStream);
procedure SaveToFile(const FileName: string);
property Items[I: Integer]: String read GetItem write SetItem;
published
property StringItem : String read FPStr write FPStr;
end;
//------------------------------------------------------------------------------
var
DataSet: TDataSet;
implementation
uses TypInfo, Sysutils;
{ TDataSet }
//------------------------------------------------------------------------------
procedure TDataSet.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('DataArray', ReadArray, WriteArray, True);
end;
//------------------------------------------------------------------------------
destructor TDataSet.Destroy;
begin
inherited;
end;
//------------------------------------------------------------------------------
function TDataSet.GetItem(I: Integer): string;
begin
Result := '';
if (I > 0) and (I < Length(FArrayToSave)) then
Result := FArrayToSave[I];
end;
//------------------------------------------------------------------------------
procedure TDataSet.SetItem(I: Integer; Value: string);
begin
if (I > 0) and (I < Length(FArrayToSave)) then
FArrayToSave[I] := Value;
end;
//------------------------------------------------------------------------------
procedure TDataSet.LoadFromFile(const FileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
//------------------------------------------------------------------------------
procedure TDataSet.LoadFromStream(const Stream: TStream);
var
Reader: TReader;
PropName, PropValue: string;
begin
Reader := TReader.Create(Stream, $FFF);
Stream.Position := 0;
Reader.ReadListBegin;
while not Reader.EndOfList do
begin
PropName := Reader.ReadString;
PropValue := Reader.ReadString;
SetPropValue(Self, PropName, PropValue);
end;
FreeAndNil(Reader);
end;
//------------------------------------------------------------------------------
procedure TDataSet.SaveToFile(const FileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
//------------------------------------------------------------------------------
procedure TDataSet.SaveToStream(const Stream: TStream);
var
PropName, PropValue: string;
cnt: Integer;
lPropInfo: PPropInfo;
lPropCount: Integer;
lPropList: PPropList;
lPropType: PPTypeInfo;
Writer: TWriter;
begin
lPropCount := GetPropList(PTypeInfo(ClassInfo), lPropList);
Writer := TWriter.Create(Stream, $FFF);
Stream.Size := 0;
Writer.WriteListBegin;
for cnt := 0 to lPropCount - 1 do
begin
lPropInfo := lPropList^[cnt];
lPropType := lPropInfo^.PropType;
if lPropType^.Kind = tkMethod then Continue;
PropName := lPropInfo.Name;
PropValue := GetPropValue(Self, lPropInfo);
Writer.WriteString(PropName);
Writer.WriteString(PropValue);
end;
Writer.WriteListEnd;
FreeAndNil(Writer);
end;
//------------------------------------------------------------------------------
constructor TDataSet.Create(aOwner: TComponent);
begin
inherited;
end;
//------------------------------------------------------------------------------
procedure TDataSet.ReadArray(Reader: TReader);
var
N: Integer;
begin
N := 0;
Reader.ReadListBegin;
while not Reader.EndOfList do begin
Reader.ReadListBegin;
FArrayToSave[N] := Reader.ReadString;
Reader.ReadListEnd;
Inc(N);
end;
Reader.ReadListEnd;
end;
//------------------------------------------------------------------------------
procedure TDataSet.WriteArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteListBegin;
for I := 1 to High(FArrayToSave) do begin
Writer.WriteListBegin;
Writer.WriteString(FArrayToSave[I]);
Writer.WriteListEnd;
end;
Writer.WriteListEnd;
end;
//------------------------------------------------------------------------------
initialization
DataSet := TDataSet.Create(Nil);
finalization
FreeAndNil(DataSet);
end.
//------------------------------------------------------------------------------
Here is my Class code re-written with Arioch's suggested code modifications from below:
unit UnitCharSett;
interface
//------------------------------------------------------------------------------
uses System.Classes;
//------------------------------------------------------------------------------
type
TCustomDatSetA = Array [0..99] of String;
TCustomCharSet = class(TComponent)
public
procedure LoadFromStream(const Stream: TStream);
procedure LoadFromFile(const FileName: string);
procedure SaveToStream(const Stream: TStream);
procedure SaveToFile(const FileName: string);
end;
TZCharSet = class(TCustomCharSet)
private
FFullArray : TCustomDatSetA;
function GetItem(I: Integer): String;
procedure SetItem(I: Integer; Value: string);
protected
procedure DefineProperties(Filer: TFiler); override;
procedure ReadArray(Reader:TReader);
procedure WriteArray(Writer:TWriter);
public
property Items[Index: Integer]: string read GetItem write SetItem;
published
end;
//------------------------------------------------------------------------------
implementation
uses
System.TypInfo, System.SysUtils;
//------------------------------------------------------------------------------
procedure TCustomCharSet.LoadFromFile(const FileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
//------------------------------------------------------------------------------
procedure TCustomCharSet.LoadFromStream(const Stream: TStream);
begin
Stream.ReadComponent(Self);
end;
//------------------------------------------------------------------------------
procedure TCustomCharSet.SaveToFile(const FileName: string);
var
Stream: TStream;
begin
Stream := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(Stream);
finally
Stream.Free;
end;
end;
//------------------------------------------------------------------------------
procedure TCustomCharSet.SaveToStream(const Stream: TStream);
begin
Stream.WriteComponent(Self);
end;
//------------------------------------------------------------------------------
{ TZCharSett }
//------------------------------------------------------------------------------
procedure TZCharSet.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('DataArray', ReadArray, WriteArray, True);
end;
//------------------------------------------------------------------------------
function TZCharSet.GetItem(I: Integer): string;
begin
Result := '';
if (I > -1) and (I < Length(FFullArray)) then
Result := FFullArray[I];
end;
//------------------------------------------------------------------------------
procedure TZCharSet.ReadArray(Reader: TReader);
var
N: Integer;
S: String;
begin
for N := Low(FFullArray) to High(FFullArray) do begin
FFullArray[N] := '';
end;
Reader.ReadListBegin;
N := Reader.ReadInteger;
if N = Length(FFullArray) then
begin
N := Low(FFullArray);
while not Reader.EndOfList do
begin
S := Reader.ReadString;
if N <= High(FFullArray) then
FFullArray[N] := S;
Inc(N);
end;
end;
Reader.ReadListEnd;
end;
//------------------------------------------------------------------------------
procedure TZCharSet.SetItem(I: Integer; Value: string);
begin
if (I > -1) and (I < Length(FFullArray)) then
FFullArray[I] := Value;
end;
//------------------------------------------------------------------------------
procedure TZCharSet.WriteArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteListBegin;
Writer.WriteInteger(Length(FFullArray));
for I := Low(FFullArray) to High(FFullArray) do begin
Writer.WriteString(FFullArray[I]);
end;
Writer.WriteListEnd;
end;
//------------------------------------------------------------------------------
initialization
RegisterClasses([TZCharSet]);
//------------------------------------------------------------------------------
end.
HOW do you actually try to read and write it ? I think you're trying to make complex incompatible things when there instead of using standard methods.
Why not to use standard VCL streaming procedures?
procedure TMyDataSet.SaveToStream(const Stream: TStream);
begin
Stream.WriteComponent(self);
end;
procedure TMyDataSet.LoadFromStream(const Stream: TStream);
begin
Stream.ReadComponent(self);
end;
However if instead of using TFiler and standard VCL streamer you make your custom code using RTTI (GetPropList) - then it would not call those virtual properties APi custom to TFiler and would only show real properties.
So my advice is just to use standard emthods like shown above and to streamline and harden the code.
And since RegisterClass works by the classname you'd better choose another name, not clashing with a real TDataSet from stock DB unit.
Fix the name and do register the class, so VCL streamer could find it by name! For example:
procedure TMyDataSet.ReadArray(Reader: TReader);
var
N: Integer; S: String;
begin
N := Low(FArrayToSave);
Reader.ReadListBegin;
while not Reader.EndOfList do begin
S := Reader.ReadString; // even if we would not save it - we should remove it from the input
if N <= High(FArrayToSave) then
FArrayToSave[N] := S;
Inc(N);
end;
Reader.ReadListEnd;
end;
procedure TMyDataSet.WriteArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteListBegin;
for I := Low(FArrayToSave) to High(FArrayToSave) do begin
Writer.WriteString(FArrayToSave[I]);
end;
Writer.WriteListEnd;
end;
initialization
DataSet := TMyDataSet.Create(Nil);
RegisterClasses([TMyDataSet]);
finalization
DataSet.Free;
end.
Additionally, i think you'd better - for future extensibility - save the array length in DFM.
procedure TMyDataSet.WriteArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteInteger(Length(FArrayToSave));
Writer.WriteListBegin;
for I := Low(FArrayToSave) to High(FArrayToSave) do begin
....
procedure TMyDataSet.ReadArray(Reader: TReader);
var
N: Integer; S: String;
begin
for N := Low(FArrayToSave) to High(FArrayToSave) do begin
FArrayToSave := ''; // in case DFM would have less elements than 50
N := Reader.ReadInteger;
if N <> Length(FArrayToSave) then... recovery from unexpected DFM version error
N := Low(FArrayToSave);
Reader.ReadListBegin;
while not Reader.EndOfList do begin
PS. you do not need {$M+} there since TComponent already is derived from TPersistent
PPS. Wanted to comment upon update in the question, but the phone refuses to do (too long?) so putting it here.
1: since we moved away from using RTTI, the Typinfo unit no more needed in uses. 2: if N = Length(FFullArray) then lacks ELSE path. Okay, now we learned that DFM is broken or incompatible, what then? I think we better raise some error. Or try to remove list of N strings, so next property could be read. Or even remove the list of elements of any type/quantity until list end. Future compatibly is never warranted, but at least some attempt can be done, even just to explicitly halt with error. Skipping reading and silently leaving the reader inside middle of property, so next properties would get crazy, I think is not the way to do it.
And generally David is correct about ignoring incorrect indices in the setter and getter. Unless you would intentionally come with some unusual pattern of implicit item creation from default template in sparse array by setting or getting with "free" "unbound" index (which is no code for either) the better approach at least in Delphi would be "fail early". That is what users of your class would expect by default. So kinda
Procedure class.CheckArrayIdx(const i: integer);
Var mx, mn : integer;
Begin
Mn := low(myarray) ; Mx := high(myarray);
If (i <= mx) and (I >= mn) then exit;
Raise ERangeError.CreateFmt('%s.Items index should be %d <= %d <= %d', [
Self.ClassName, mn, I, mx]) ;
End;
This procedure can be called as 1st line in both setter and getter. Then you can just work with surely correct index value.

Resources