How to use kbmMWConfiguration.Configuration as Memory only - delphi

I know, that I can use the Configuration object to create files:
// Config.PrepareYAMLStorage;
// Config.PrepareBSONStorage;
// Config.PrepareJSONStorage;
Config.PrepareXMLStorage;
These files are created automatically when the object is destroyed. I want to use the class as memory only, so I can persist the content of Config.Storage on a database.
Is there a variant of Storage, that has something like a Storage.AsString or how can I create a replacement of the exiting Storage, that can do this?

There are a couple of ways.
The "intended" way is to create your own storage method for the configuration object.
If you want to have the data as JSON, you can inherit from kbmMW 's TkbmMWJSONConfigurationStorage and add a couple of methods that provides a string with the JSON data in it.
Eg.
TMyStorage = class(TkbmMWJSONConfigurationStorage)
protected
function GetConfigurationAsString:string;
procedure SetConfigurationAsString(const AString:string);
public
property ConfigurationAsString:string read GetConfigurationAsString write SetConfigurationAsString;
end;
...
function TMyStorage.GetConfigurationAsString:string;
var
str:TkbmMWJSONStreamer;
begin
str:=TkbmMWJSONStreamer.Create;
try
Result:=str.SaveToUTF16String(&ON);
finally
str.Free;
end;
end;
procedure TMyStorage.SetConfigurationAsString(const AString:string);
var
str:TkbmMWJSONStreamer;
co:TkbmMWONCustomObject;
begin
str:=TkbmMWJSONStreamer.Create;
try
co:=str.LoadFromUTF16String(AString);
if co.IsObject then
begin
&ON.Free;
&ON:=TkbmMWONObject(co);
end
else
co.Free;
finally
str.Free;
end;
end;
Then
var
myStorageInstance:TMyStorage;
begin
myStorageInstance:= TMyStorage.Create('somepathtojsonstorage',false);
Configuration.Storage:=myStorageInstance;
end;
Notice that this will still save and load the configuration to and from a file. If you want to prevent that, override the Save and Load methods and make them do nothing.
But it does provide you with a way to access the JSON storage as a string.
It however also prevents you from switching the storage out at will, since a complete registry based storage usually will not easily be able to be set from a string or read to a string.
/Kim/C4D

Related

How to tell if an object is created from Streaming or not

I have two separate requirements to detect if an object is being created from a stream while in the Create / AfterConstruction code.
In the first case I have an existing object which is being refeactored so it is implemented as a component to allow consuming users to drop the component on a form or data module. One of the properties of this component is a Uuid which needs to be assigned uniquely to each object instance, and needs to remain unique for that object instance across different runs of the program. Internally the Uuid is held in our own class but we present a UuidString property to the user in the IDE. I need to know whether to allocate a Uuid and register the component on first creation, or wait until the Loaded routine (which is never called if it's not read from a stream).
In the second case I have a set of components that provide an 'OnReady' event to the application. Once the object is completely initialised (which could be asynchronous) the event is called. If the object is being streamed then I can override Loaded method to undertake additional configuiration, but if it's not being streamed Loaded will never be called and I should start the additional work in AfterConstruction.
Looking at the documentation I though I could use:
if( not (csLoading in Self.ComponentState) ) then
...
Or, to catch newly created objects in the designer specifically:
if( (csDesigning in Self.ComponentState) And
not (csLoading in Self.ComponentState) ) then
...
However having looked into the Code (I'm not really a Delphi programmer by background) I see that csLoading is only set after the Create / AfterConstruction has executed.
During Create / AfterConstruction execution is there anyway I can tell if Loaded is going to be called?
I have realised that all components created by streaming will have Owner<>nil but it's expected that components created at runtime would normally have Owner<>nil as well.
My only thought at the moment is to see if the owner is loading with something like:
if( (Self.Owner<>nil) And (not (csLoading in Self.Owner.ComponentState)) ) then
...
Is this the correct approach? Or is there a better 'Delphi Way' to do it?
Assuming your property is named UUIDString backed by a field FUUIDString. Then this approach should work:
type
TMyComponent = class(TComponent)
private
FUUIDString: string;
function GetUUIDString: string;
protected
procedure Loaded; override;
public
property UUIDString: string read GetUUIDString write FUUIDString;
end;
function TMyComponent.GetUUIDString: string;
begin
if FUUIDString = '' then
FUUIDString := CreateNewUUIDString;
Result := FUUIDString;
end;
procedure TMyComponent.Loaded;
begin
inherited;
RegisterUUIDString(UUIDString);
end;
If UUIDString is loaded during the stream reading it will contain the stored value. Otherwise the register call inside Loaded will generate a new one.
You were pretty close to a solution. You just needed to override the correct procedure.
I’m using "CreateWnd" for the following reasons:
I need to initialise images that require the parent (Panel) to have a Handle ready, so this procedure is the right place to do that.
It is called when ComponentState is established (with csDesigning and/or csLoading).
Any Published Property is available (has been streamed in from the DFM) at this point.
It is only called once.
The component I’m working on is a “Picture- Button”, made form a TCustomPanel with a TSpeedButton on it and up to 4 images also on the panel.
So I wanted to put initial (default) images on the panel, which requires determining when the component is first created (dropped on the form).
See some of the code below >>
………
protected
{ Protected declarations }
procedure CreateWnd; override;
procedure DoInitialConfig;
………
procedure TJEPicButton.CreateWnd;
begin
inherited;
if (csDesigning in ComponentState) and not (csLoading in ComponentState) then
begin // Initial State Only
DoInitialConfig;
end;
// More code here....
end;
procedure TJEPicButton.DoInitialConfig;
begin
// Load initial (default) image(s) on panel
// The user can (using Object Inspector) replace this image(s) with his own…
end;

Delphi - Read in a Large File using IFuture from the Parallel Programming Library

I'm reading in some large(ish) excel file which take "ages" to load. I can load it in before I really need to access it. So I thought this would be a good use for an IFuture from the Parallel Programming Library. But I'm not sure how to go about it as all of the "Future" examples only cover simple types such as strings, integers etc.
Here's the non-parallel code:
xls := TsmXLSFile.Create;
xls.Open(s);
Where "xls" is the Excel object and "s" is a memory stream.
How would a "Future" go about this? Would I declare xls as...
xls := IFuture<TsmXLSFile>
Is this correct. If it is then do I need to free it like a normal TsmXLSFile since it's now an interface?
Steve
Declare a field to get hold of that interface:
FXlsFuture: IFuture<TsmXLSFile>;
Add a method to create that future and another one to handle the loaded file:
function TForm90.CreateXlsFuture: IFuture<TsmXLSFile>;
begin
{ starts loading }
Result := TTask.Future<TsmXLSFile>(
function: TsmXLSFile
begin
result := TsmXLSFile.Create;
result.Open(s);
end);
end;
procedure TForm90.HandleXlsFuture(AFuture: IFuture<TsmXLSFile>);
var
xsl: TsmXLSFile;
begin
xsl := AFuture.Value; { eventually blocks until the file is loaded }
{ do something with the file }
xsl.Free;
end;
In addition you can query the Status of the future to check if the file is already loaded to avoid blocking.
No, you can't do that. You would do it more like this:
... // this must be a persistant object
ismXLSFile : IFuture< TsmXLSFile >;
...
// Get started
ismXLSFile := TTask.Future< TsmXLFile > (function : TsmXLFile begin Result := TsmXLFile.Create ); end; );
ismXLSFile.Start;
// Then at some later point
xls := ismXLSFile.Value;
And yes, you do still need to free it. xls is not an interfaced object. (ismXLSFile is).

Delphi: Correct way to store objects fetched from TObjectList

This example is of course simplified, but basically I have a main form that triggers another form (frmSettings) with
function Execute(var aSettings: TSettings):Boolean
TSettings is my own object created in main form for keeping track of the settings.
In this newly opened form (frmSettings) I fetch a TMyObjectList that is a descendant from TObjectList.
It's filled with TMyObj.
I then fill a TListBox with values from that TMyObjectList.
the code:
...
FMyObjectList : TMyObjectList;
property MyObjectList: TMyObjectList read getMyObjectList;
...
function TfrmSettings.getMyObjectList: TMyObjectList ;
begin
If not Assigned(FMyObjectList) then FMyObjectList := TMyObjectList.Create(True)
Result := FMyObjectList;
end;
function TfrmSettings.Execute(var aSettings: TSettings): Boolean;
begin
//Fill myObjectList
FetchObjs(myObjectList);
//Show list to user
FillList(ListBox1, myObjectList);
//Show form
ShowModal;
Result := self.ModalResult = mrOk;
if Result then
begin
// Save the selected object, but how??
// Store only pointer? Lost if list is destroyed.. no good
//Settings.selectedObj := myObjectList.Items[ListBox1.ItemIndex];
// Or store a new object? Have to check if exist already?
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
end;
end;
procedure TfrmSettings.FillList(listBox: TListBox; myObjectList: TMyObjectList);
var
i: Integer;
begin
listBox.Clear;
With myObjectList do
begin
for i := 0 to Count - 1 do
begin
//list names to user
listBox.Items.Add(Items[i].Name);
end;
end;
end;
procedure TfrmSettings.FormDestroy(Sender: TObject);
begin
FreeAndNil(FMyObjectList);
end;
Storing just the pointer doesn't seem as a good idea, as triggering the settings form again, recreates the list, and the original object would be lost even if user hits "cancel"
So storing a copy seems better, using assign to get all the properties correct. And first checking if I already have an object.
If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
Should I move those two lines to a method instead like Settings.AssignSelectedObj(aMyObj:TMyObj)
Does this look correct or am I implementing this the wrong way?
Something more/less needed?
I need some guidelines so I feel more secure that I don't open up for memory leaks and other trouble.
Other than that reviewing the code a bit, the real question is: Is this the correct way to store my SelectedObject in the settings class?
Is this the correct way to store the selected object in the settings?
Probably not. Your settings class should not depend on the form in any way. What if you decide to create and destroy your form dynamically each time the user opens the settings? In this case your settings would hold an invalid object reference.
IMHO it is better to store the object list in the settings together with the index of the selected object. The form should just access the settings, fill the list box and modify the selected object index after the user confirmed with OK.
You are producing a memory leak in your code. You create a TObjectList as a local variable but you never free it. And if you free the local variable, the object references in the listbox will be invalid. You have two options:
Store the object list as a member variable of your form, create in the FromCreate event handler and destroy it in the FormDestroy event handler. You can then safely use object references in your list box.
Store the object list somewhere outside and pass it into the form as a parameter of the Execute method. In this scenario, you can also safely use object references.
I would rename myObjectList to GlobalObjectList, and move it out of the class. It can be declared in the form, but create/free in the initialization/finalization sections. During initialization, after you create the list, populate it from the ini file (or wherever you store it). Now you can access it from anywhere that has your unit in the Uses.
What about the serialization of TSettings? Put your settings in some published properties, then let the RTTI save its content:
type
TSettings = class(TPersistent)
public
function SaveAsText: UTF8String;
end;
function TSettings.SaveAsText: UTF8String;
begin
var
Stream1, Stream2: TMemoryStream;
begin
Stream1 := TMemoryStream.Create;
Stream2 := TMemoryStream.Create;
try
Stream1.WriteComponent(MyComponent);
ObjectBinaryToText(Stream1, Stream2);
SetString(result,PAnsiChar(Stream2.Memory),Stream2.Size);
finally
Stream1.Free;
Stream2.Free;
end;
end;
Then your settings can be stored in a text file or text string.
It's just one solution. But storing settings as text is very handy. We use such an approach in our framework, to store settings via a code-generated user interface. A settings tree is created, from a tree of TPersistent instances.

Persistent Objects in Windows XP/Delphi 7

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.

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