Persistent Objects in Windows XP/Delphi 7 - delphi

I am trying to make an AlarmSystem in Delphi 7, Windows XP. I have to register alarms in a Database (MS SQL Server 2000). But what if the server is down??? Well, I can imagine that I have to persist objects of TAlarm type. So, how can I do this? Maybe inheriting from TComponent??? Please, how can I do this??
Thanks a lot.
I am sorry about my English.
Here you have more info...
TAlarm is a class that descends from TObject, basically. There are 10 more classes that descend from TAlarm (some types of alarms). TAlarm has a field named FParams : TParams, and the child classes only have an Execute method. The field FParams can be of different types: TAlarmX1_Params, TAlarmX2_Params, etc, etc, etc.

You can inheriting from TPersistent and then you can use the TJvAppXMLFileStorage (JVCL) component to serialize the TAlarm class.
Save a Object
uses
JvAppXMLStorage;
Procedure SaveMyObject(MyAlarm : TAlarm)
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.WritePersistent('', MyAlarm);
MyStore.Xml.SaveToFile('C:\MyAlarm.xml');
finally
MyStore.Free;
end;
end;
Restore a Object
uses
JvAppXMLStorage;
Procedure LoadMyObject(MyAlarm : TAlarm)
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\MyAlarm.xml');
MyStore.ReadPersistent('', MyAlarm);
finally
MyStore.Free;
end;
end;
UPDATE
If you need to persist more than one object to the XML file you must assign a path (unique id) to the WritePersistent and ReadPersistent methods.
See this example,
Multiple Persist
Procedure SaveMyObjects(MyObjects : Array of TComponent);
var
MyStore: TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
for i := Low(MyObjects) to High(MyObjects) do
MyStore.WritePersistent(MyObjects[i].Name, MyObjects[i]); //In this case i use the name property of the component.
MyStore.Xml.SaveToFile('C:\Tools\MyAlarm.xml');
finally
MyStore.Free;
end;
end;
to save the components
SaveMyObjects([Button1,Button2,Edit1,Edit2]);
Multiple LOAD
Procedure LoadMyObjects(MyObjects:Array of TComponent);
var
MyStore : TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\Tools\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\Tools\MyAlarm.xml');
for i := Low(MyObjects) to High(MyObjects) do
MyStore.ReadPersistent(MyObjects[i].Name, MyObjects[i]);
finally
MyStore.Free;
end;
end;
To restore the properties
LoadMyObjects([Button1,Button2,Edit1,Edit2]);
Another option to load
Procedure LoadMyObjectById(Id:String;MyObject:TComponent); //using the id of the object
var
MyStore : TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\Tools\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\Tools\MyAlarm.xml');
MyStore.ReadPersistent(id, MyObject);
finally
MyStore.Free;
end;
end;
you must run it this way
LoadMyObjectById(Button1.Name,Button1); //Again using the Name property.
I hope this example will be useful ;)

You could persist the information in an XML or INI file locally. That doesn't require changing what TAlarm descends from. You would need to manually persist and restore all the properties that you wish to persist locally though. Shouldn't be that complicated.

If the server where you're supposed to be saving your data to is down, the best course of action is usually to cause the operation to fail and return an error. This way you don't need two separate sets of serialization code, both of which have to be kept in sync with each other, and a way to take your local data and upload it to the server once it's back up.
Plus, if your app depends on a remote server, it's likely that the user won't be able to do much with it offline anyway, so this isn't as bad of an idea as it may dound at first from a user-interface perspective.

I used a local database, an Access mdb file accessed thru ADO, with the same schema than the server. When connection recovers, I did a synchronization. But, nowadays, I have dropped this technique; wnen connection is lost or server is down, the application fails.

Related

How to insert records with DataSnap

In many tutorials i read how to select data from a database in a datasnap client, p.e. to complete a dbgrid.
But i need now to know how to insert or update a row, p.e "new client". Can everybody recommends me a book or tutorial?
I have an sqlconnection on a clientdatamodule on the clientside apart from clientclassesunit. I was prooving wuth an SQLQuery with an insert SQL Statement but it doen't function.
On the other han i have on the server side:
procedure TServerMethods1.nuevocheque(idcliente,numero,cuenta,idbanco : integer; fr,fc, titular:string ;importe:Double;cobrado:Boolean);
var
ucheque:integer;
begin
with qicheque do
begin
Open;
ParamByName('idcliente').AsInteger:=idcliente;
ParamByName('numero').AsInteger:=numero;
ParamByName('fr').AsDate:=StrToDate(fr);
ParamByName('fc').AsDate:=StrToDate(fc);
ParamByName('importe').AsFloat:=importe;
ParamByName('titular').AsString:=titular;
ParamByName('cobrado').AsBoolean:=cobrado;
ParamByName('cuenta').AsInteger:=cuenta;
ExecSQL();
end;
end;
With this method i try to insert, the statement is into SQL property of the component.
On the client side, i have a TSQLServerMethod wich calls "nuevocheque":
procedure TForm4.BGuardarClick(Sender: TObject);
var
idcliente,numero,cuenta,idbanco:integer;
titular:string;
cobrado:Boolean;
fr,fc:string;
importe:Double;
begin
ClientModule1.nuevocheque.Create(nil);
with ClientModule1.nuevocheque do
begin
idcliente:=1;
numero:=StrToInt(ENumero.Text);
cuenta:=StrToInt(Ecuenta.Text);
idbanco:=1;
titular:=ENombre.Text;
cobrado:=False;
importe:=StrToFloat(EMonto.Text);
fr:=EFechaEmision.Text;
fc:=EFechacobro.Text;
end;
end;
But it doesn´t function.
Thank for your help
Well, i achieve inserting data into mysql database i had desgined.
This is te code in delphi into a button:
procedure TForm4.BGuardarClick(Sender: TObject);
var
idcliente,numero,cuenta,idbanco:integer;
titular:string;
cobrado:Boolean;
fr,fc:string;
importe:Double;
a:TServerMethods1Client;
interes:Double;
begin
a:=TServerMethods1Client.Create(ClientModule1.SQLConnection1.DBXConnection);
begin
idcliente:=Unit3.id;
numero:=StrToInt(ENumero.Text);
cuenta:=StrToInt(Ecuenta.Text);
idbanco:=lcbbanco.KeyValue;
titular:=ENombre.Text;
cobrado:=False;
if (EP.Text<>'') then
begin
importe:=StrToFloat(EHC.Text);
end
else
begin
importe:=StrToFloat(EMonto.Text);
end;
fr:=EFechaEmision.Text;
fc:=EFechacobro.Text;
end;
a.nuevocheque(idcliente,numero,cuenta, idbanco,fr,fc,titular,importe,cobrado);
end;
I've called to method create() with the SQL component such as M Diwo said me.
Im too hapy. Thanks to all
I don't know what you use as database connection, for my own convenience I have slightly modified for dbGO (parameters passed by variant).
Also I have made a function from the server method, like this the client can be notified that there has been a problem (with the query, connection,...). Here is the server method:
//server
function TServerMethods1.NuevoCheque(idcliente, numero, cuenta,
idbanco: integer; fr, fc, titular: string; importe: Double;
cobrado: Boolean): Boolean;
begin
try
with qicheque, Parameters do
begin
Close;
ParamByName('idcliente').Value:=idcliente;
ParamByName('numero').Value:=numero;
ParamByName('fr').Value:=StrToDate(fr);
ParamByName('fc').Value:=StrToDate(fc);
ParamByName('importe').Value:=importe;
ParamByName('titular').Value:=titular;
ParamByName('cobrado').Value:=cobrado;
ParamByName('cuenta').Value:=cuenta;
ExecSQL();
end;
Result := true;
except
Result := false;
//raise; <-- uncomment if you want to handle this properly in your code
end;
end;
For the client I suppose you generated a proxy unit that generally creates an object called ServerMethods1 ?
You must pass the client dbx connection to this - I say this because I saw you put nil in your code.
// client
procedure TfrmClient.BGuardaClick(Sender: TObject);
var
sm : TServerMethods1Client; // <-- generated by proxy generator
idcliente,numero,cuenta,idbanco : integer;
fr,fc, titular : string ;
importe : Double;
cobrado : Boolean;
begin
sm := TServerMethods1Client.Create(SQL.DBXConnection);
if sm.nuevocheque(idcliente,numero,cuenta,idbanco, fr,fc, titular, importe, cobrado) then
// ok
else
// error
sm.Free;
end;
hth
You can use calls to remote methods, but they won't automatically update your data aware controls automatically. Datasnap is able to handle it. First, you need to add/update/remove data on the client. It happens in the local cache managed by the TClientDataset, even when you "Post".
When you're ready, you need to "apply" changes to the remote server calling the Apply() method.
When you call it, the provider component on the server receives a "delta" with the record to change from the client dataset, and will automatically generate the needed INSERT/UPDATED/DELETE SQL statements.
If you don't like them, or you need to perform more complex processing, you can use the provider events to perform the needed operations yourself for each changed record and then tell the provider you did it to avoid the automatic processing. Then the provider passes back the "delta" to the client, where it is used to updated the data aware controls. You can also modify the "delta" before it is passed back.
Read in the documentation the explanation of the Datasnap architecture - it's a multistep design where several components work to allow for a multi-tier implementation.

Delphi: Store data in somekind of structure

For a simulation program I'm working in Delphi 2010. The simulation isn't a problem but I need to use large collection of data which gives a problem. The data is available in excel sheets, so there is no need to edit this data in Delphi, but collecting this data from the excel sheets takes around 10min. This isn't a problem as long as you don't need to collect the data every time the program runs. So I made a program which collects all the data makes it visible, not problems here,and then store it. However I can't store it to a "Delphi format" , without losing the structure, so it can be loaded in a few seconds.
I'm not that experienced in Delphi and I searched a long time for the solution but couldn't understand what was best. I think my way of structuring the data is wrong but it was simple and worked. However if there are better ways of storing the data please say so, but remember that I need some more explanation than just use 'a xml file', 'generict, or 'Ttreeview'. (have read it but wasn't able to use it).
The data is for: I made this product, The next product I make is this, so do I need to clean? True or false.
The data is stores as a class(TObject) with Productnumber (integer) and a List which contains all products that could be made next.This list contains another class(TObject) with an Productnumber (integer) and a do I need to clean(boolean). I want to save this structure in a file, without losing the data and read it back to the same structure.
I hope someone could help. Thank you in advance.
Update: The code to provide a little more information (modified to English)
Clean_from = class(TObject)
public
myfromNumber : Integer;
mylist : TList;
published
constructor Create;
End
Clean_To = class(TObject)
public
myToNumber : Integer;
Clean : Boolean;
End;
constructor Clean_from.Create;
begin
inherited Create;
myList := Tlist.Create;
end;
For i = 0 to 100 do
begin
From:= Clean_from.create;
for j := 0 to 10 do
begin
To := Clean_To.create;
To.clean := true or false;
From.myList.add(To);
end;
GlobalList.add(from);
end;
And now I want to save the global list with all the content so I could load it with the same structure.
What you need is the so-called "serialization" mechanism.
1. The standard way
1.1 SaveToStream
In Delphi, we usually implement a SaveToStream method, which will save the content of each object in a destination TStream (either a TFileStream or a TMemoryStream).
You'll have to write the serialization by hand.
1.2 DFM-like streaming
See TWriter / TReader classes.
If you define your data in published properties, you are able to serialize them using those standard Delphi classes.
For some methods able to serialize any TCollection to and from JSON content, see this blog article.
2. The RTTI
See for instance this SO question.
In particular, the new enhanced RTTI (available since Delphi 2010) opens new opportunities to serialization.
3. Use records instead of classes
If each item does not store a lot of content (some integer/boolean), it may make sense to use records instead of objects. For speed and memory consumption/fragmentation, it may be worth it.
Here is some wrapper able to serialize any dynamic array, even containing nested records or dynamic arrays.
4. Use a database engine
Perhaps the better approach is not to have your data stuck in a non-evolving binary form, proprietary to your application. If you want to add a property, you'll have to manage it by hand. Or if you want to access your data from other applications, it may be difficult.
There are a lot of database solutions around - instead of using an external database (like MS SQL, FireBird or Oracle), it could be a good idea to embed the database inside your application (much easier to install). Worth mentioning SQLite which has a lot of wrappers, including our version (which will allow you to change to any other database if you want to use MS SQL or Oracle instead).
You have other solutions around - see this SO question - and if you need performance, take a look at our Big Table library.
Add SaveToStream() and LoadFromStream() methods to your data object which, well, save the data to a stream and load data from a stream.
type
TMyData = class(TObject)
private
FChildProducts: TList;
FProductnumber : integer;
FClean: boolean;
public
procedure LoadFromStream(const aStream: TStream);
procedure SaveToStream(const aStream: TStream);
published
property Productnumber: Integer read FProductnumber write FProductnumber;
property Clean: Boolean reas FClean write FClean;
end;
procedure TMyData.LoadFromStream(const aStream: TStream);
var x, cnt: Integer;
cD: TMyData;
begin
aStream.Read(FProductnumber, SizeOf(FProductnumber));
aStream.Read(FClean, SizeOf(FClean));
// read number of child products
aStream.Read(cnt, SizeOf(cnt));
// load child objects
for x := 1 to cnt do begin
cD := TMyData.create;
cD.LoadFromStream(aStream);
FChildProducts.Add(cD);
end;
end;
procedure TMyData.SaveToStream(const aStream: TStream);
var x: Integer;
begin
aStream.Write(FProductnumber, SizeOf(FProductnumber));
aStream.Write(FClean, SizeOf(FClean));
// save number of child products
x := FChildProducts.Count;
aStream.Write(x, SizeOf(x));
// save child objects
for x := 0 to FChildProducts.Count - 1 do
(FChildProducts[x] as TMyData).SaveToStream(aStream);
end;
I assume you have some list of "root objects" so you can make an function or method which saves/loads them to/from stream ie
function SaveDataList(const List: TList;const aFileName: string);
var x: Integer;
FS: TFileStream;
begin
FS := TFileStream.Create(aFileName, ...);
try
// save file version
x := 1;
FS.Write(x, SizeOf(x));
// save number of products
x := List.Count;
FS.Write(x, SizeOf(x));
// save objects
for x := 0 to List.Count - 1 do
(List[x] as TMyData).SaveToStream(FS);
finally
FS.Free;
end;
end;
This is the general idea... how to load data back should be clear too. The file version thing is there so that when the data object changes (ie you add some property) you can increment the version number so that in the loading code you can load data into right version of the data object.

Delphi (win32) serialization libraries

Are there any Delphi serialization libraries that are capable of serializing records and arrays of records instead of classes?
#Max you can use the TJvAppXMLFileStorage component from JEDI to serialize an record or an array of records.
you can use the procedure called WriteBinary to store the data and ReadBinary to read.
unfortunately there is not much documentation on this component, so here you have an very simple example for store a single record (for an array of records you can easily modify this source code).
The record structure
type
MyRecord= record
Field1 : Integer;
Field2 : Double;
Field3 : String[20];
Field4 : String[20];
end;
Save an record
Procedure SaveMyRecord(Rec : MyRecord);
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\temp\record.xml';
//this component supports store multiples objects to the same file, so you need use an identifier for you particular object, in this case i'm use the Dummy name.
MyStore.WriteBinary('Dummy', #Rec,sizeof(Rec));
MyStore.Xml.SaveToFile(MyStore.FileName);
finally
MyStore.Free;
end;
end;
this procedure create an XML file like this, the data is encoded in an hexadecimal format.
<?xml version="1.0" encoding="iso-8859-1"?>
<Configuration>
<Dummy>84030000000000003333333333331F400D737472696E6720746573742031000000000000000D737472696E672074657374203200000000000000000000000000</Dummy>
</Configuration>
Read the persisted data
Procedure LoadMyRecord(var Rec : MyRecord);
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\temp\record.xml';//point to the same file
MyStore.Xml.LoadFromFile(MyStore.FileName); //load the file
MyStore.ReadBinary('Dummy', #Rec,sizeof(Rec));//use the Dummy identifier and pass the record as an pointer
finally
MyStore.Free;
end;
end;
Check this full project (tested in Delphi 7)
program ProjectPersistRecord;
{$APPTYPE CONSOLE}
uses
SysUtils,
JvAppXMLStorage;
type
MyRecord= record
Field1 : Integer;
Field2 : Double;
Field3 : String[20];
Field4 : String[20];
end;
Procedure SaveMyRecord(Rec : MyRecord);
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\temp\record.xml';
MyStore.WriteBinary('Dummy', #Rec,sizeof(Rec));
MyStore.Xml.SaveToFile(MyStore.FileName);
finally
MyStore.Free;
end;
end;
Procedure LoadMyRecord(var Rec : MyRecord);
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\temp\record.xml';
MyStore.Xml.LoadFromFile(MyStore.FileName);
MyStore.ReadBinary('Dummy', #Rec,sizeof(Rec));
finally
MyStore.Free;
end;
end;
Var
Rec : MyRecord;
begin
//Fill the record
Rec.Field1:=900;
Rec.Field2:=7.8;
Rec.Field3:='string test 1';
Rec.Field4:='string test 2';
SaveMyRecord(Rec); //save the record
FillChar(Rec,SizeOf(Rec),#0); //clear the record variable
LoadMyRecord(Rec);//restire the record data
//show the loaded data
Writeln(rec.field1);
Writeln(rec.field2);
Writeln(rec.field3);
Writeln(rec.field4);
Readln;
end.
If you have Delphi 2010, you might want to take a look at DeHL. It contains a serialization library that can handle pretty much any data type.
Another solution, working from Delphi 5 up to XE2, is available in one of our OpenSource unit.
In fact, it implements:
Some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
A dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString. You have also JSON serialization at hand, including custom layout - see sustom JSON serialization of records.
Being a record, if you don't have properties, I don't believe you're any further ahead, trying to use any of the persistence frameworks (like DeHL).
The accepted answer, while technically correct, is dubious in real-world utility, and has many long term support-nightmare scenarios if you use it in production. DON'T DO IT.
If your program is just a bit of ad-hoc'ery, may I humbly suggest that you rock it old-school with a "file of record", a classic Turbo Pascal technique that still works.

problem subclassing TTreeNode in delphi

i'm writing a delphi 2009 app that uses a TTreeView on a docking panel.
i saw i could make big simplifications in my app if i subclassed the TTreeNode. the tree view it's on is placed on a docking panel.
TInfoTreeNode=class(TTreeNode)
private
// remember some stuff
public
end;
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
NodeClass:=TInfoTreeNode;
end;
i think i've hit a wall though...each "TInfoTreeNode" instance needs to remember things about itself. since the handles are freed when the panel containing the TTreeView auto-hides, the classes are destroyed.
that's a problem because then everything the classes knew is then forgotten.
is there a way around this (other than reloading every TInfoTreeNode from the database again)?
thank you!
IIRC, the Tag Data property on each TTreeNode instance is preserved through the handle rebuild.
You could either use this as an index into a List containing objects with additional information, or use type-casting to store an object reference and access the objects directly.
the problem is caused by the wrong implementation of your custom TreeNode - it doesn't preserve its information when the TreeView's parent window gets recreated after it has been hodden. As a solution, create a TTreeView descendant and override its DestroyWnd method, to preserve your custom values. For example, take a look at how the TCustomTreeView.DestroyWnd method is implemented.
Having looked at TCustomTreeView.DestroyWnd like Joe Meyer proposes, I would suggest you revert to using the TreeNode.Data property, and store a reference to objects of a new class inheriting from TObject directly. The OnDeletion event of the TreeView offers a good spot to put the destruction code: "TMyObject(Node.Data).Free;"
Usage is pretty similar except you'll need to use "TMyObject(Node.Data)" instead of "TMyNode(Node)". A warning though: experience has taught me to pay close attention not to forget the ".Data" part, since "TMyObject(Node)" will not throw a compile error and raise access violations at run-time.
thank you all for your replies!
i have for 10 years been using the tree view using TTreeNode's data property. i wanted to be free of:
setting the Data property
creating/destroying the "data" object in a manner so there are no memory leaks
i have used the Data property for an ID number in the past as well.
today, my nodes have GUIDs to find their data in the database so they don't "fit" into the Data property anymore.
using a descendant of TTreeNode seems to have addressed my wishes nicely but in order to make that work nicely i had to do a few things:
handle TTreeView.OnCreateNodeClass event
handle TTreeView.OnDeletion event to retrieve latest data from the nodes before they are destroyed
handle TTreeView.OnAddition event to: 1) maintain a simple list of the nodes 2) set the node's Data property so we can use it to find the place in the list allocated for storing it's data.
here's the code:
TInfoTreeNodeMemory=record
...
end;
TInfoTreeNode=class(TTreeNode)
private
m_rInfoTreeNodeMemory:TInfoTreeNodeMemory;
public
property InfoTreeNodeMemory:TInfoTreeNodeMemory read m_rInfoTreeNodeMemory write m_rInfoTreeNodeMemory;
end;
TInfoTreeNodeMemoryItemList=class
private
m_List:TList<TInfoTreeNodeMemory>;
public
constructor Create;
destructor Destroy; override;
procedure HandleOnDeletion(Node: TInfoTreeNode);
procedure HandleOnAddition(Node: TInfoTreeNode);
end;
TfraInfoTree = class(TFrame)
tvInfo: TTreeView;
procedure tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
procedure tvInfoDeletion(Sender: TObject; Node: TTreeNode);
procedure tvInfoAddition(Sender: TObject; Node: TTreeNode);
private
m_NodeMemory:TInfoTreeNodeMemoryItemList;
...
procedure TfraInfoTree.tvInfoCreateNodeClass(Sender: TCustomTreeView;
var NodeClass: TTreeNodeClass);
begin
// THIS IS VITAL!
NodeClass:=TInfoTreeNode;
end;
procedure TfraInfoTree.tvInfoDeletion(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnDeletion(TInfoTreeNode(Node));
end;
procedure TfraInfoTree.tvInfoAddition(Sender: TObject; Node: TTreeNode);
begin
m_NodeMemory.HandleOnAddition(TInfoTreeNode(Node));
end;
g_icTreeNodeNotInList=MAXINT;
procedure TInfoTreeNodeMemoryItemList.HandleOnDeletion(Node: TInfoTreeNode);
var
iPosition:integer;
begin
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
raise Exception.Create('Node memory not found!')
else
// we recognize this node; store his data so we can give it back to him later
m_List[iPosition]:=Node.InfoTreeNodeMemory;
end;
procedure TInfoTreeNodeMemoryItemList.HandleOnAddition(Node: TInfoTreeNode);
var
iPosition:integer;
begin
// "coat check" for getting back node data later
iPosition:=integer(Node.Data);
if iPosition=g_icTreeNodeNotInList then
begin
// Node.Data = index of it's data
// can't set Node.Data in OnDeletion so we must assign it in OnAddition instead
Node.Data:=pointer(m_List.Count);
// this data may very well be blank; it mostly occupies space; we harvest the real data in OnDeletion
m_List.Add(Node.InfoTreeNodeMemory);
end
else
// we recognize this node; give him his data back
Node.InfoTreeNodeMemory:=m_List[iPosition];
end;
very cool...it meets all my objectives!
to add a node to the tree, all i need to do is:
// g_icTreeNodeNotInList important so the "coat check" (TInfoTreeNodeMemoryItemList)
// can recognize this as something that's not in it's list yet.
MyInfoTreeNode:=TInfoTreeNode(tvInfo.Items.AddChildObject(nParent, sText, pointer(g_icTreeNodeNotInList))));

Persisting more than one object in Delphi 7 [duplicate]

I am trying to make an AlarmSystem in Delphi 7, Windows XP. I have to register alarms in a Database (MS SQL Server 2000). But what if the server is down??? Well, I can imagine that I have to persist objects of TAlarm type. So, how can I do this? Maybe inheriting from TComponent??? Please, how can I do this??
Thanks a lot.
I am sorry about my English.
Here you have more info...
TAlarm is a class that descends from TObject, basically. There are 10 more classes that descend from TAlarm (some types of alarms). TAlarm has a field named FParams : TParams, and the child classes only have an Execute method. The field FParams can be of different types: TAlarmX1_Params, TAlarmX2_Params, etc, etc, etc.
You can inheriting from TPersistent and then you can use the TJvAppXMLFileStorage (JVCL) component to serialize the TAlarm class.
Save a Object
uses
JvAppXMLStorage;
Procedure SaveMyObject(MyAlarm : TAlarm)
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.WritePersistent('', MyAlarm);
MyStore.Xml.SaveToFile('C:\MyAlarm.xml');
finally
MyStore.Free;
end;
end;
Restore a Object
uses
JvAppXMLStorage;
Procedure LoadMyObject(MyAlarm : TAlarm)
var
MyStore: TJvAppXMLFileStorage;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\MyAlarm.xml');
MyStore.ReadPersistent('', MyAlarm);
finally
MyStore.Free;
end;
end;
UPDATE
If you need to persist more than one object to the XML file you must assign a path (unique id) to the WritePersistent and ReadPersistent methods.
See this example,
Multiple Persist
Procedure SaveMyObjects(MyObjects : Array of TComponent);
var
MyStore: TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
for i := Low(MyObjects) to High(MyObjects) do
MyStore.WritePersistent(MyObjects[i].Name, MyObjects[i]); //In this case i use the name property of the component.
MyStore.Xml.SaveToFile('C:\Tools\MyAlarm.xml');
finally
MyStore.Free;
end;
end;
to save the components
SaveMyObjects([Button1,Button2,Edit1,Edit2]);
Multiple LOAD
Procedure LoadMyObjects(MyObjects:Array of TComponent);
var
MyStore : TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\Tools\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\Tools\MyAlarm.xml');
for i := Low(MyObjects) to High(MyObjects) do
MyStore.ReadPersistent(MyObjects[i].Name, MyObjects[i]);
finally
MyStore.Free;
end;
end;
To restore the properties
LoadMyObjects([Button1,Button2,Edit1,Edit2]);
Another option to load
Procedure LoadMyObjectById(Id:String;MyObject:TComponent); //using the id of the object
var
MyStore : TJvAppXMLFileStorage;
i : integer;
begin
MyStore:= TJvAppXMLFileStorage.Create(nil);
try
MyStore.FileName:='C:\Tools\MyAlarm.xml';
MyStore.Xml.LoadFromFile('C:\Tools\MyAlarm.xml');
MyStore.ReadPersistent(id, MyObject);
finally
MyStore.Free;
end;
end;
you must run it this way
LoadMyObjectById(Button1.Name,Button1); //Again using the Name property.
I hope this example will be useful ;)
You could persist the information in an XML or INI file locally. That doesn't require changing what TAlarm descends from. You would need to manually persist and restore all the properties that you wish to persist locally though. Shouldn't be that complicated.
If the server where you're supposed to be saving your data to is down, the best course of action is usually to cause the operation to fail and return an error. This way you don't need two separate sets of serialization code, both of which have to be kept in sync with each other, and a way to take your local data and upload it to the server once it's back up.
Plus, if your app depends on a remote server, it's likely that the user won't be able to do much with it offline anyway, so this isn't as bad of an idea as it may dound at first from a user-interface perspective.
I used a local database, an Access mdb file accessed thru ADO, with the same schema than the server. When connection recovers, I did a synchronization. But, nowadays, I have dropped this technique; wnen connection is lost or server is down, the application fails.

Resources