I have a list of records. Each record has an
URL:= string
field. Via GUI the user can edit revords or even delete records (rows) entirely. I would like to download in background in a thread all online files pointed by the URL field. Of course, I don't want to lock the GUI when the thread downloads the files. So, how do I make sure the program/user cannot access the record processed currently by the thread?
I really like to use BITS for downloads.
Access from Delphi is easy. In BITS your define jobs, which are downloaded in background. When ready you can call a EXE, you can poll in the idle loop for the result or you can get an event.
Here is a samples - you will need the jedi lib!
That sample needs to be extended for production quality (error handling, logging, job name)!
unit uc_DownloadBits;
interface
uses
ExtActns;
type
TDownloadBits = class
public
class procedure DownloadForground(ziel, downloadurl: WideString; DownloadFeedback:TDownloadProgressEvent);
class procedure DownloadBackground(ziel, downloadurl, ExeName, Params: WideString);
class procedure CompleteJob(JobId: WideString);
end;
implementation
uses
ComObj, ActiveX, SysUtils,
JwaBits, JwaBits1_5, Windows;
{ TDownloadBits }
class procedure TDownloadBits.CompleteJob(JobId: WideString);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
g: TGuid;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
g:=StringToGUID(jobid);
bi.GetJob(g,job);
job.Complete();
end;
class procedure TDownloadBits.DownloadBackground(ziel, downloadurl,
ExeName, Params: WideString);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
job2: IBackgroundCopyJob2;
jobId: TGUID;
r: HRESULT;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
r:=bi.CreateJob('Updatedownload', BG_JOB_TYPE_DOWNLOAD, JobId, job);
if not Succeeded(r) then
raise Exception.Create('Create Job Failed');
r:=Job.AddFile(PWideChar(downloadurl), PWideChar(ziel));
if not Succeeded(r) then
raise Exception.Create('Add File Failed');
// Download starten
Job.Resume();
Params:=Params+' '+GUIDToString(jobId);
Job2 := Job as IBackgroundCopyJob2;
Job2.SetNotifyCmdLine(pWideChar(ExeName), PWideChar(Params));
Job.SetNotifyFlags(BG_NOTIFY_JOB_TRANSFERRED);
end;
class procedure TDownloadBits.DownloadForground(ziel, downloadurl: widestring; DownloadFeedback:TDownloadProgressEvent);
var
bi: IBackgroundCopyManager;
job: IBackgroundCopyJob;
jobId: TGUID;
r: HRESULT;
// Status Zeug
p: BG_JOB_PROGRESS;
s: BG_JOB_STATE;
// Timer Zeug
hTimer: THandle;
DueTime: TLargeInteger;
c: boolean;
begin
bi:=CreateComObject(CLSID_BackgroundCopyManager) as IBackgroundCopyManager;
r:=bi.CreateJob('Updatedownload', BG_JOB_TYPE_DOWNLOAD, JobId, job);
if not Succeeded(r) then
raise Exception.Create('Create Job Failed');
r:=Job.AddFile(PWideChar(downloadurl), PWideChar(ziel));
if not Succeeded(r) then
raise Exception.Create('Add File Failed');
// Download starten
Job.Resume();
DueTime:=-10000000;
hTimer:=CreateWaitableTimer(nil, false, 'EinTimer');
SetWaitableTimer(hTimer, DueTime, 1000, nil, nil, false);
while True do
begin
Job.GetState(s);
if s in [BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_TRANSFERRED] then
begin
Job.GetProgress(p);
DownloadFeedback(nil, p.BytesTransferred, p.BytesTotal, dsDownloadingData, '', c);
if c then
break;
end;
if s in [BG_JOB_STATE_TRANSFERRED,
BG_JOB_STATE_ERROR,
BG_JOB_STATE_TRANSIENT_ERROR] then
break;
WaitForSingleObject(hTimer, INFINITE);
end;
CancelWaitableTimer(hTimer);
CloseHandle(hTimer);
if s=BG_JOB_STATE_TRANSFERRED then
job.Complete();
job:=nil;
bi:=nil;
end;
end.
So, how do I make sure the program/user cannot access the record
processed currently by the thread?
In "modern" (sine Delphi 2006 I think) records you can use properties with getters and setters just as with classes. In the setter you can prevent or allow changes to the underlying field.
A naive example:
type
TMyRecord = record
private
FURL: string;
FDownloading: boolean;
procedure SetTheURL(NewURL: string);
public
property TheURL: string read FURL write SetTheURL;
procedure DownLoad;
end;
procedure TMyRecord.SetTheURL(NewURL: string);
begin
if not FDownloading then
FURL := NewURL;
else
// signal inability to change
end;
procedure TMyRecord.DownLoad;
begin
FDownLoading := True;
// hand the downloading task to a thread
end;
Here's the documentation under Records(advanced)
Following is based on the solution of Tom Brunberg using a record. Idea that the record will begin downloading via a TThread (the implementation of the download itself is out of the question as I understand). It might be a bit rough, let me know if there are severe mistakes for example in handling the thread.
While downloading, the data is not accessible, I decided to throw an exception when accessed, but that's up to implementation details of the GUI. property IsDownLoading: Boolean can be used to e.g. disable controls that would normally make the data accessible, too.
Still, the URL can be changed at all times by user, terminating the current download if in process.
A TDownloadThread should only be present while needed. If there are lots of these records, this should reduce unneeded resources.
unit Unit1;
interface
uses
System.Classes, System.SysUtils;
type
TDownLoadThread = class(TThread)
private
FURL: string;
FData: Variant;
procedure SetURL(const Value: string);
protected
procedure Execute; override;
public
property Data: Variant read FData;
property URL: string read FURL write SetURL;
end;
TDownLoadRecord = record
private
FData: Variant;
FURL: string;
FDownLoadThread: TDownLoadThread;
procedure DownLoadThreadTerminate(Sender: TObject);
function GetIsDownLoading: Boolean;
procedure SetURL(const Value: string);
procedure URLChanged;
function GetData: Variant;
public
property Data: Variant read GetData;
property URL: string read FURL write SetURL;
property IsDownLoading: Boolean read GetIsDownLoading;
end;
implementation
{ TDownLoadRecord }
procedure TDownLoadRecord.DownLoadThreadTerminate(Sender: TObject);
begin
FData := FDownLoadThread.Data;
FDownLoadThread := nil;
end;
function TDownLoadRecord.GetData: Variant;
begin
if not IsDownLoading then
Result := FData
else
raise Exception.Create('Still downloading');
end;
function TDownLoadRecord.GetIsDownLoading: Boolean;
begin
Result := (FDownLoadThread <> nil) and not FDownLoadThread.Finished;
end;
procedure TDownLoadRecord.SetURL(const Value: string);
begin
if FURL <> Value then
begin
FURL := Value;
URLChanged;
end;
end;
procedure TDownLoadRecord.URLChanged;
begin
if FURL <> '' then
begin
if FDownLoadThread <> nil then
TDownLoadThread.Create(True)
else
if not FDownLoadThread.CheckTerminated then
FDownLoadThread.Terminate;
FDownLoadThread.URL := FURL;
FDownLoadThread.FreeOnTerminate := True;
FDownLoadThread.OnTerminate := DownLoadThreadTerminate;
FDownLoadThread.Start;
end;
end;
{ TDownLoadThread }
procedure TDownLoadThread.Execute;
begin
// Download
end;
procedure TDownLoadThread.SetURL(const Value: string);
begin
FURL := Value;
end;
end.
Related
I modified #Stefan Glienkes example from Notify the TObjectList when Object changed to use IList, since I am using interfaced objects in my list.
In the event handler, I can handle caAdded and caRemoved events, but caChanged is not signaled.
Is this by design or am I making a mistake somewhere?
This example shows the behavior:
program Project61;
{$APPTYPE CONSOLE}
uses
Spring,
Spring.Collections,
SysUtils;
type
TNotifyPropertyChangedBase = class(TInterfaceBase, INotifyPropertyChanged)
private
fOnPropertyChanged: Event<TPropertyChangedEvent>;
function GetOnPropertyChanged: IPropertyChangedEvent;
protected
procedure PropertyChanged(const propertyName: string);
end;
IMyInterface = interface(IInterface)
['{D5966D7D-1F4D-4EA8-B196-CB9B39AF446E}']
function GetName: String;
procedure SetName(const Value: String);
property Name: String read GetName write SetName;
end;
TMyObject = class(TNotifyPropertyChangedBase, IMyInterface)
private
FName: string;
function GetName: string;
procedure SetName(const Value: string);
public
property Name: string read GetName write SetName;
end;
TMain = class
procedure ListChanged(Sender: TObject; const item: IMyInterface;
action: TCollectionChangedAction);
end;
{ TNotifyPropertyChangedBase }
function TNotifyPropertyChangedBase.GetOnPropertyChanged: IPropertyChangedEvent;
begin
Result := fOnPropertyChanged;
end;
procedure TNotifyPropertyChangedBase.PropertyChanged(
const propertyName: string);
begin
fOnPropertyChanged.Invoke(Self,
TPropertyChangedEventArgs.Create(propertyName) as IPropertyChangedEventArgs);
end;
{ TMyObject }
procedure TMyObject.SetName(const Value: string);
begin
FName := Value;
PropertyChanged('Name');
end;
function TMyObject.GetName: string;
begin
Result := FName;
end;
{ TMain }
procedure TMain.ListChanged(Sender: TObject; const item: IMyInterface;
action: TCollectionChangedAction);
begin
case action of
caAdded:
Writeln('item added ', item.Name);
caRemoved, caExtracted:
Writeln('item removed ', item.Name);
caChanged:
Writeln('item changed ', item.Name);
end;
end;
var
main: TMain;
list: IList<IMyInterface>;
o : IMyInterface;
begin
list := TCollections.CreateList<IMyInterface>;
list.OnChanged.Add(main.ListChanged);
o := TMyObject.Create;
o.Name := 'o1';
list.Add(o); // triggering caAdded
o := TMyObject.Create;
o.Name := 'o2';
list.Add(o); // triggering caAdded
list[1].Name := 'o3'; // not triggering caChanged
list.Remove(o); // triggering caRemoved
Readln;
end.
The lists created by TCollections.CreateList, TCollections.CreateObjectList or TCollections.CreateInterfaceList don't support INotifyPropertyChanged.
You see that TCollections.CreateObservableList which I used in my example is contraint to only hold objects as these are typically candidates for implementing property change notification as PODOs are imo usually bad candidates to be used as interfaces.
You can probably still code your own version of that list that accepts interfaces and queries them for INotifyPropertyChanged.
This question already has an answer here:
Delphi Access Violation when moving button on form
(1 answer)
Closed 7 years ago.
I'm trying to search for all files in all subfolders so it takes long time and application stop responding, so I used Thread (it's first time work with Threads) I read about it and I found this way to create and execute threads, but nothing happen when I call the thread, and I don't understand why I couldn't use the added components on the main form, I had to re-declare it again?
what I miss here?
type
TSearchThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
procedure AddAllFilesInDir(const Path: string; ListBox:TsListBox);
var
SR: TSearchRec;
I: Integer;
begin
if FindFirst(IncludeTrailingBackslash(Path) + '*.*', faAnyFile or faDirectory, SR) = 0 then
try
repeat
if (SR.Attr and faDirectory) = 0 then
ListBox.Items.Add(Path+'\'+SR.Name)
else if (SR.Name <> '.') and (SR.Name <> '..') then
AddAllFilesInDir(IncludeTrailingBackslash(Path) + SR.Name, ListBox);
Form1.sPanel2.Caption := Path+'\'+SR.Name;
Form1.sPanel2.Refresh;
ListBox.Refresh;
until FindNext(Sr) <> 0;
finally
FindClose(SR);
end;
end;
procedure TSearchThread.Execute;
var FileList: TsListBox;
I: Integer;
{Here I had to re-declare objects}
sDirectoryEdit1: TsDirectoryEdit;
sListBox1: TsListBox;
begin
FileList := TsListBox.Create(nil);
FileList.Parent := sListBox1;
FileList.Visible := False;
AddAllFilesInDir(sDirectoryEdit1.Text+'\', FileList);
for I := 0 to FileList.Count -1 do
if sListBox1.Items.IndexOf(FileList.Items.Strings[I]) = -1 then
sListBox1.Items.Add(FileList.Items.Strings[I]);
FileList.Clear;
end;
procedure TForm1.sDirectoryEdit1Change(Sender: TObject);
begin
TSearchThread.Create(False);
end;
Ok, let me give it a try:
First a new version of your thread:
uses
IOUtils;
type
TFileFoundEvent = procedure(const Path: string; const SearchRec: TSearchRec) of object;
TSearchThread = class(TThread)
private
FPath: string;
FSearchRec: TSearchRec;
FFileFoundEvent: TFileFoundEvent;
protected
procedure Execute; override;
public
Constructor Create(const aPath: string; aFileFoundEvent: TFileFoundEvent); reintroduce;
end;
{ TSearchThread }
constructor TSearchThread.Create(const aPath: string; aFileFoundEvent: TFileFoundEvent);
begin
// Create the Thread non suspended
inherited Create(false);
// Copy parameters to local members.
FFileFoundEvent := aFileFoundEvent;
FPath := aPath;
// Make the sure the thread frees itself after execution
FreeOnTerminate := True;
end;
procedure TSearchThread.Execute;
var
FilterPredicate: TDirectory.TFilterPredicate;
begin
// FilterPredicate is an in-place anonymous method to be called each time the TDirectory.GetFiles finds a file
FilterPredicate := function(const Path: string; const SearchRec: TSearchRec): Boolean
begin
// Since we can not access from within Synchronize we need to copy iot to a member of the class
FSearchRec := SearchRec;
// You cannot access VCL objects directly from a thread.
// So you need to call Syncronize
// For more info look in the online help
// http://docwiki.embarcadero.com/Libraries/Seattle/en/System.Classes.TThread.Synchronize
Synchronize(nil,
procedure
begin
FFileFoundEvent(FPath, FSearchRec);
end);
Result := True;
end;
// Do the search
TDirectory.GetFiles(FPath, TSearchOption.soTopDirectoryOnly, FilterPredicate)
end;
The main diffrence are that I pass a callback proceudre onto the constructor of the thread. And ofcause I uses TDirectory.GetFiles to search for files. You'll find TDirectory.GetFiles in IOUtils
Then you need to use it: Place a Listbox on your from and then call it like this :
Form definition:
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
private
procedure FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
public
{ Public declarations }
end;
...
implementation
procedure TForm1.FileFoundEvent(const Path: string; const SearchRec: TSearchRec);
begin
ListBox1.Items.Add(SearchRec.Name);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TSearchThread.Create(ExtractFilePath(Application.ExeName), FileFoundEvent);
end;
If you don't want to see the ongoing results of the searching, but rather want some speed you can create a version of the searchthread that gives you the result all at once:
uses
IOUtils;
type
TSearchThread = class(TThread)
private
FSearchPath: String;
FResultBuffer: TStrings;
protected
procedure Execute; override;
public
constructor Create(const aSearchPath: string; aResultBuffer: TStrings); overload;
end;
constructor TSearchThread.Create(const aSearchPath: string; aResultBuffer: TStrings);
begin
inherited Create(false);
FSearchPath := IncludeTrailingPathDelimiter(aSearchPath);
FResultBuffer := aResultBuffer;
FreeOnTerminate := True;
end;
procedure TSearchThread.Execute;
var
FBuffer: TStringlist;
Filename: String;
begin
Synchronize(nil,
procedure
begin
FResultBuffer.Text := 'Searching ' + FSearchPath;
end);
FBuffer := TStringlist.Create;
for Filename in TDirectory.GetFiles(FSearchPath, TSearchOption.soAllDirectories, nil) do
FBuffer.Add(Filename);
Synchronize(nil,
procedure
begin
FResultBuffer.Assign(FBuffer);
end);
FreeAndNil(FBuffer);
end;
This thread you have to call in a bit diffent way.
The form setup i still the same as before: A Listbox on a Form.
type
TForm1 = class(TForm)
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
private
Stopwatch: TStopwatch;
procedure SearchThreadTerminate(Sender: TObject);
public
{ Public declarations }
end;
And then the implementation:
procedure TForm1.FormCreate(Sender: TObject);
begin
Stopwatch := TStopwatch.StartNew;
with TSearchThread.Create('C:\Program Files (x86)\Embarcadero\', ListBox1.Items) do
OnTerminate := SearchThreadTerminate;
end;
procedure TForm1.SearchThreadTerminate(Sender: TObject);
begin
Stopwatch.Stop;
Caption := 'Elapsed Milliseconds: ' + IntToStr(Stopwatch.ElapsedMilliseconds) + ' Files found: ' + IntToStr(ListBox1.Items.Count);
end;
The advantage of this version is speed. Updaing the screen is slow, and the first solution updated the screen for each file it found, while this one only updates the screen twice.
Try it out.
Can the object of (TObjectList) know when some values of (TMyObject) was changed?
Some example:
TMyObject = class
oName: string;
end;
TMyObjectList = class(TObjectList<TMyObject>)
end;
procedure Form1.Button1.Click(Sender: TObject);
var
Obj: TMyObject;
List: TMyObjectList;
Begin
List:= TMyObjectList.Create;
Obj:= TMyObject.Create;
List.Add(Obj);
List[0].oName:= 'Test'; // here a want to know from var (List) when this object (Obj or List[0]) changed his value..
end;
Thanks for any help.
I just added the TObservableList<T> type to Spring4D (feature/observablelist branch). It is mostly modeled after .NET and uses the INotifyPropertyChanged interface to attach its event handler to any objects that support it. This class has been part of DSharp for quite some time and is used in production. It might change a bit in the future and become full part of the library.
Here is a small example how to use it so you get an idea:
program Project60;
{$APPTYPE CONSOLE}
uses
Spring,
Spring.Collections,
SysUtils;
type
TNotifyPropertyChangedBase = class(TInterfaceBase, INotifyPropertyChanged)
private
fOnPropertyChanged: Event<TPropertyChangedEvent>;
function GetOnPropertyChanged: IPropertyChangedEvent;
protected
procedure PropertyChanged(const propertyName: string);
end;
TMyObject = class(TNotifyPropertyChangedBase)
private
fName: string;
procedure SetName(const Value: string);
public
property Name: string read fName write SetName;
end;
TMain = class
procedure ListChanged(Sender: TObject; const item: TMyObject;
action: TCollectionChangedAction);
end;
{ TNotifyPropertyChangedBase }
function TNotifyPropertyChangedBase.GetOnPropertyChanged: IPropertyChangedEvent;
begin
Result := fOnPropertyChanged;
end;
procedure TNotifyPropertyChangedBase.PropertyChanged(
const propertyName: string);
begin
fOnPropertyChanged.Invoke(Self,
TPropertyChangedEventArgs.Create(propertyName) as IPropertyChangedEventArgs);
end;
{ TMyObject }
procedure TMyObject.SetName(const Value: string);
begin
fName := Value;
PropertyChanged('Name');
end;
{ TMain }
procedure TMain.ListChanged(Sender: TObject; const item: TMyObject;
action: TCollectionChangedAction);
begin
case action of
caAdded: Writeln('item added ', item.Name);
caRemoved, caExtracted: Writeln('item removed ', item.Name);
caChanged: Writeln('item changed ', item.Name);
end;
end;
var
main: TMain;
list: IList<TMyObject>;
o: TMyObject;
begin
list := TCollections.CreateObservableList<TMyObject>;
list.OnChanged.Add(main.ListChanged);
o := TMyObject.Create;
o.Name := 'o1';
list.Add(o);
o := TMyObject.Create;
o.Name := 'o2';
list.Add(o);
list[1].Name := 'o3';
Readln;
end.
There is nothing built in that can do what you ask. You will need to implement a notification mechanism yourself. This is the classic scenario for the Observer Pattern.
There are many implementations of this pattern already in existence. One obvious choice would be to use the implementation in Spring4D. Nick Hodges recent book, More Coding in Delphi, includes a chapter on this pattern which I would recommend.
Found the way, how to call method of TObjectList from TMyObject. Using TNotifyEvent in base Object.
Example:
TMyClass = class(TObject)
private
FName: string;
FOnNameEvent: TNotifyEvent;
procedure SetName(value: string);
public
property Name: string read FName write SetName;
property OnNameEvent: TNotifyEvent read FOnNameEvent write FOnNameEvent;
end;
procedure TMyClass.SetName(value: string);
begin
FName := value;
if Assigned(FOnNameEvent) then
FOnNameEvent(Self);
end;
procedure MyNameEvent(Sender: TObject);
var
i: Integer;
begin
for i := 0 to MyListOfMyClassObjects.Count -1 do
if Sender = MyListOfMyClassObjects.Item[i] then
begin
MessageBox(0, PChar(TMyClass(MyListOfMyClassObjects.Item[i]).Name), nil, MB_OK);
break;
end;
end;
procedure MyProc;
var
MyObject: TMyClass;
begin
MyObject := TMyClass.Create;
MyObject.OnNameEvent := MyNameEvent;
MyListOfMyClassObjects.Add(MyObject);
end;
We have reasonably large XML strings which we currently parse using MSXML2
I have just tried using MSXML6 hoping for a speed improvement and have got nothing!
We currently create a lot of DOM Documents and I guess there may be some overhead in constantly interacting with the MSXML2/6 dll
Does anyone know of a better/faster XML component for Delphi?
If anyone can suggest an alternative, and it is faster, we would look to integrate it, but that would be a lot of work, so hopefully the structure would not be too different to that used by MSXML
We are using Delphi 2010
Paul
some time ago I had to serialize record to XML format; for ex:
TTest = record
a : integer;
b : real;
end;
to
<Data>
<a type="tkInteger">value</a>
<b type="tkFloat">value</b>
</Data>
I used RTTI to recursively navigate through record fields and storing values to XML.
I've tried few XML Parsers. I did't need DOM model to create xml, but needed it to load it back.
XML contained about 310k nodes (10-15MBytes);
results presented in table below, there are 6 columns with time in seconds;
1 - time for creating nodes and write values
2 - SaveToFile();
3 = 1 + 2
4 - LoadFromFile();
5 - navigate through nodes and read values
6 = 4 + 5
MSXML/Xerces/ADOM - are differend vendors for TXMLDocument (DOMVendor)
JanXML doesn't work with unicode; I fixed some errors, and saved XML, but loading causes AV (or stack overflow, I don't remember);
manual - means manually writing XML using TStringStream.
I used Delphi2010, Win7x32, Q8200 CPU/2.3GHz, 4Gb of RAM.
update: You can download source code for this test (record serialization to XML using RTTI) here http://blog.karelia.pro/teran/files/2012/03/XMLTest.zip All parsers (Omni, Native, Jan) are included (now nodes count in XML is about 270k), sorry there are no comments in code.
I know that it's an old question, but people might find it interesting:
I wrote a new XML library for Delphi (OXml): http://www.kluug.net/oxml.php
It features direct XML handling (read+write), SAX parser, DOM and a sequential DOM parser.
One of the benefits is that OXml supports Delphi 6-Delphi XE5, FPC/Lazarus and C++Builder on all platforms (Win, MacOSX, Linux, iOS, Android).
OXml DOM is record/pointer based and offers better performance than any other XML library:
The read test returns the time the parser needs to read a custom XML DOM from a file (column "load") and to write node values to a constant dummy function (column "navigate").
The file is encoded in UTF-8 and it's size is about 5,6 MB.
The write test returns the time the parser needs to create a DOM (column "create") and write this DOM to a file (column "save").
The file is encoded in UTF-8 and it's size is about 11 MB.
+ The poor OmniXML (original) writing performance was the result of the fact that OmniXML didn't use buffering for writing. Thus writing to a TFileStream was very slow. I updated OmniXML and added buffering support. You can get the latest OmniXML code from the SVN.
Recently I had a similar issue where using the MSXML DOM parser proved to be too slow for the given task. I had to parse rather large documents > 1MB and the memory consumption of the DOM parser was prohibitive.
My solution was to not use a DOM parser at all, but to go with the event driven MSXML SAX parser. This proved to be much, much faster. Unfortunately the programming model is totally different, but dependent on the task, it might be worth it.
Craig Murphy has published an excellent article on how to use the MSXML SAX parser in delphi:
SAX, Delphi and Ex Em El
Someday I have written very simple XML test suite. It serves MSXML (D7 MSXML3?), Omni XML (bit old) and Jedi XML (latest stable).
Test results for 1,52 MB file:
XML file loading time MSXML: 240,20 [ms]
XML node selections MSXML: 1,09 [s]
XML file loading time OmniXML: 2,25 [s]
XML node selections OmniXML: 1,22 [s]
XML file loading time JclSimpleXML: 2,11 [s]
and access violation for JclSimpleXML node selections :|
Unfortunately I actually haven't much time to correct above AV, but sorces are contained below...
fmuMain.pas
program XmlEngines;
uses
FastMM4,
Forms,
fmuMain in 'fmuMain.pas' {fmMain},
uXmlEngines in 'uXmlEngines.pas',
ifcXmlEngine in 'ifcXmlEngine.pas';
{$R *.res}
begin
Application.Initialize;
Application.Title := 'XML Engine Tester';
Application.CreateForm(TfmMain, fmMain);
Application.Run;
end.
fmuMain.pas
unit fmuMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, xmldom, XMLIntf, msxmldom, XMLDoc,
//
ifcXmlEngine, StdCtrls;
type
TfmMain = class(TForm)
mmoDebug: TMemo;
dlgOpen: TOpenDialog;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure mmoDebugClick(Sender: TObject);
private
fXmlEngines: TInterfaceList;
function Get_Engine(const aIx: Integer): IXmlEngine;
protected
property XmlEngine[const aIx: Integer]: IXmlEngine read Get_Engine;
procedure Debug(const aInfo: string); // inline
public
procedure RegisterXmlEngine(const aEngine: IXmlEngine);
end;
var
fmMain: TfmMain;
implementation
{$R *.dfm}
uses
uXmlEngines, TZTools;
{ TForm1 }
function TfmMain.Get_Engine(const aIx: Integer): IXmlEngine;
begin
Result:= nil;
Supports(fXmlEngines[aIx], IXmlEngine, Result)
end;
procedure TfmMain.RegisterXmlEngine(const aEngine: IXmlEngine);
var
Ix: Integer;
begin
if aEngine = nil then
Exit; // WARRNING: program flow disorder
for Ix:= 0 to Pred(fXmlEngines.Count) do
if XmlEngine[Ix] = aEngine then
Exit; // WARRNING: program flow disorder
fXmlEngines.Add(aEngine)
end;
procedure TfmMain.FormCreate(Sender: TObject);
begin
fXmlEngines:= TInterfaceList.Create();
dlgOpen.InitialDir:= ExtractFileDir(ParamStr(0));
RegisterXmlEngine(TMsxmlEngine.Create(Self));
RegisterXmlEngine(TOmniXmlEngine.Create());
RegisterXmlEngine(TJediXmlEngine.Create());
end;
procedure TfmMain.mmoDebugClick(Sender: TObject);
procedure TestEngines(const aFilename: TFileName);
procedure TestEngine(const aEngine: IXmlEngine);
var
PerfCheck: TPerfCheck;
Ix: Integer;
begin
PerfCheck := TPerfCheck.Create();
try
PerfCheck.Init(True);
PerfCheck.Start();
aEngine.Load(aFilename);
PerfCheck.Pause();
Debug(Format(
'XML file loading time %s: %s',
[aEngine.Get_ID(), PerfCheck.TimeStr()]));
if aEngine.Get_ValidNode() then
begin
PerfCheck.Start();
for Ix:= 0 to 999999 do
if aEngine.Get_ChildsCount() > 0 then
begin
aEngine.SelectChild(Ix mod aEngine.Get_ChildsCount());
end
else
aEngine.SelectRootNode();
PerfCheck.Pause();
Debug(Format(
'XML nodes selections %s: %s',
[aEngine.Get_ID(), PerfCheck.TimeStr()]));
end
finally
PerfCheck.Free();
end
end;
var
Ix: Integer;
begin
Debug(aFilename);
for Ix:= 0 to Pred(fXmlEngines.Count) do
TestEngine(XmlEngine[Ix])
end;
var
CursorBckp: TCursor;
begin
if dlgOpen.Execute() then
begin
CursorBckp:= Cursor;
Self.Cursor:= crHourGlass;
mmoDebug.Cursor:= crHourGlass;
try
TestEngines(dlgOpen.FileName)
finally
Self.Cursor:= CursorBckp;
mmoDebug.Cursor:= CursorBckp;
end
end
end;
procedure TfmMain.Debug(const aInfo: string);
begin
mmoDebug.Lines.Add(aInfo)
end;
procedure TfmMain.FormDestroy(Sender: TObject);
begin
fXmlEngines.Free()
end;
end.
ifcXmlEngine.pas
unit ifcXmlEngine;
interface
uses
SysUtils;
type
TFileName = SysUtils.TFileName;
IXmlEngine = interface
['{AF77333B-9873-4FDE-A3B1-260C7A4D3357}']
procedure Load(const aFilename: TFileName);
procedure SelectRootNode();
procedure SelectChild(const aIndex: Integer);
procedure SelectParent();
//
function Get_ID(): string;
function Get_ValidNode(): Boolean;
function Get_ChildsCount(): Integer;
function Get_HaveParent(): Boolean;
//function Get_NodeName(): Boolean;
end;
implementation
end.
uXmlEngines.pas
unit uXmlEngines;
interface
uses
Classes,
//
XMLDoc, XMLIntf, OmniXml, JclSimpleXml,
//
ifcXmlEngine;
type
TMsxmlEngine = class(TInterfacedObject, IXmlEngine)
private
fXmlDoc: XMLDoc.TXMLDocument;
fNode: XMLIntf.IXMLNode;
protected
public
constructor Create(const aOwner: TComponent);
destructor Destroy; override;
procedure Load(const aFilename: TFileName);
procedure SelectRootNode();
procedure SelectChild(const aIndex: Integer);
procedure SelectParent();
//
function Get_ID(): string;
function Get_ValidNode(): Boolean;
function Get_ChildsCount(): Integer;
function Get_HaveParent(): Boolean;
//function Get_NodeName(): Boolean;
end;
TOmniXmlEngine = class(TInterfacedObject, IXmlEngine)
private
fXmlDoc: OmniXml.IXmlDocument;
fNode: OmniXml.IXMLNode;
protected
public
constructor Create;
destructor Destroy; override;
procedure Load(const aFilename: TFileName);
procedure SelectRootNode();
procedure SelectChild(const aIndex: Integer);
procedure SelectParent();
//
function Get_ID(): string;
function Get_ValidNode(): Boolean;
function Get_ChildsCount(): Integer;
function Get_HaveParent(): Boolean;
//function Get_NodeName(): Boolean;
end;
TJediXmlEngine = class(TInterfacedObject, IXmlEngine)
private
fXmlDoc: TJclSimpleXML;
fNode: TJclSimpleXMLElem;
protected
public
constructor Create();
destructor Destroy(); override;
procedure Load(const aFilename: TFileName);
procedure SelectRootNode();
procedure SelectChild(const aIndex: Integer);
procedure SelectParent();
//
function Get_ID(): string;
function Get_ValidNode(): Boolean;
function Get_ChildsCount(): Integer;
function Get_HaveParent(): Boolean;
//function Get_NodeName(): Boolean;
end;
implementation
uses
SysUtils;
{ TMsxmlEngine }
constructor TMsxmlEngine.Create(const aOwner: TComponent);
begin
if aOwner = nil then
raise Exception.Create('TMsxmlEngine.Create() -> invalid owner');
inherited Create();
fXmlDoc:= XmlDoc.TXmlDocument.Create(aOwner);
fXmlDoc.ParseOptions:= [poPreserveWhiteSpace]
end;
destructor TMsxmlEngine.Destroy;
begin
fXmlDoc.Free();
inherited Destroy()
end;
function TMsxmlEngine.Get_ChildsCount: Integer;
begin
Result:= fNode.ChildNodes.Count
end;
function TMsxmlEngine.Get_HaveParent: Boolean;
begin
Result:= fNode.ParentNode <> nil
end;
function TMsxmlEngine.Get_ID: string;
begin
Result:= 'MSXML'
end;
//function TMsxmlEngine.Get_NodeName: Boolean;
//begin
// Result:= fNode.Text
//end;
function TMsxmlEngine.Get_ValidNode: Boolean;
begin
Result:= fNode <> nil
end;
procedure TMsxmlEngine.Load(const aFilename: TFileName);
begin
fXmlDoc.LoadFromFile(aFilename);
SelectRootNode()
end;
procedure TMsxmlEngine.SelectChild(const aIndex: Integer);
begin
fNode:= fNode.ChildNodes.Get(aIndex)
end;
procedure TMsxmlEngine.SelectParent;
begin
fNode:= fNode.ParentNode
end;
procedure TMsxmlEngine.SelectRootNode;
begin
fNode:= fXmlDoc.DocumentElement
end;
{ TOmniXmlEngine }
constructor TOmniXmlEngine.Create;
begin
inherited Create();
fXmlDoc:= OmniXml.TXMLDocument.Create();
fXmlDoc.PreserveWhiteSpace:= true
end;
destructor TOmniXmlEngine.Destroy;
begin
fXmlDoc:= nil;
inherited Destroy()
end;
function TOmniXmlEngine.Get_ChildsCount: Integer;
begin
Result:= fNode.ChildNodes.Length
end;
function TOmniXmlEngine.Get_HaveParent: Boolean;
begin
Result:= fNode.ParentNode <> nil
end;
function TOmniXmlEngine.Get_ID: string;
begin
Result:= 'OmniXML'
end;
//function TOmniXmlEngine.Get_NodeName: Boolean;
//begin
// Result:= fNode.NodeName
//end;
function TOmniXmlEngine.Get_ValidNode: Boolean;
begin
Result:= fNode <> nil
end;
procedure TOmniXmlEngine.Load(const aFilename: TFileName);
begin
fXmlDoc.Load(aFilename);
SelectRootNode()
end;
procedure TOmniXmlEngine.SelectChild(const aIndex: Integer);
begin
fNode:= fNode.ChildNodes.Item[aIndex]
end;
procedure TOmniXmlEngine.SelectParent;
begin
fNode:= fNode.ParentNode
end;
procedure TOmniXmlEngine.SelectRootNode;
begin
fNode:= fXmlDoc.DocumentElement
end;
{ TJediXmlEngine }
constructor TJediXmlEngine.Create;
begin
inherited Create();
fXmlDoc:= TJclSimpleXML.Create();
end;
destructor TJediXmlEngine.Destroy;
begin
fXmlDoc.Free();
inherited Destroy()
end;
function TJediXmlEngine.Get_ChildsCount: Integer;
begin
Result:= fNode.ChildsCount
end;
function TJediXmlEngine.Get_HaveParent: Boolean;
begin
Result:= fNode.Parent <> nil
end;
function TJediXmlEngine.Get_ID: string;
begin
Result:= 'JclSimpleXML';
end;
//function TJediXmlEngine.Get_NodeName: Boolean;
//begin
// Result:= fNode.Name
//end;
function TJediXmlEngine.Get_ValidNode: Boolean;
begin
Result:= fNode <> nil
end;
procedure TJediXmlEngine.Load(const aFilename: TFileName);
begin
fXmlDoc.LoadFromFile(aFilename);
SelectRootNode()
end;
procedure TJediXmlEngine.SelectChild(const aIndex: Integer);
begin
fNode:= fNode.Items[aIndex]
end;
procedure TJediXmlEngine.SelectParent;
begin
fNode:= fNode.Parent
end;
procedure TJediXmlEngine.SelectRootNode;
begin
fNode:= fXmlDoc.Root
end;
end.
Give a try to himXML by himitsu.
It is released under MPL v1.1 , GPL v3.0 or LGPL v3.0 license.
You will have to register to the Delphi-Praxis (german) excellent Delphi site so as to be able to download:
himxml_246.7z
It has a very impressive performance and the distribution includes demos demonstrating that. I've successfully used it in Delphi 2007, Delphi 2010 and Delphi XE.
From the research I've done so far, I'm already guessing the answer is no but just to make sure... (also, this entry can be updated once support for this is available).
The question title should already be self-sufficient I think, but FWIW what I'm trying to do is this: I have a configuration framework built around record constants: Every configuration option available in my app is defined in a central place in the form of a typed constant, which contains the name of the registry (or INI) key, its data type and its default value. These constants are what I pass to the accessor methods in my framework which then implements the necessary logic for retrieving and storing the option values.
I'd now like to extend the information in those records to also include meta data that I can use to auto-generate ADM/ADMX files (ifdef'ed out in the release builds) describing those options.
But for that I'd need to be able to enumerate those constants, unless I add some sort of explicit registration mechanism which seems like unnecessary duplication.
Ideally, instead of adding additional fields to the record type I would have preferred to declare the meta info in the form of attributes but those cannot (yet?) be applied to constants. Also, this wouldn't change anything about the necessity of enumerating the constants in the first place.
Assuming that this currently isn't possible via RTTI, I will probably consider putting the meta data into comments and somehow parsing that out. That'll likely be another question here.
[platform info: currently using Delphi 2010, but I already have an XE license - just didn't have time to install it, yet]
Long answer coming up .... :-)
Instead of trying to enumerate global constants, you might want to try a different approach to what you're doing.
Some time ago, Robert Love had a very interesting idea.
He uses custom attributes and RTTI to specify how to store and retrieve values from a .ini file.
In his blog he's got a great explanation on how it works:
http://robstechcorner.blogspot.com/2009/10/ini-persistence-rtti-way.html
I've expanded on that a bit in the code below:
You can now have other types than strings only (string, integer, double, boolean).
You can specify a default value in your attributes.
There's a base settings class to inherit from. You can set a filename for the inifile here, and it does loading and saving for you.
Base AppSettings class.. TAppSettings automatically stores settings in a file in this format: <yourappname>.config.ini
Example... When I want to have database settings stored in an ini file, all I need to do is instantiate a TDbSettings. You don't need to know how or where the values are actually stored, and access is really fast.
var
DbSettings : TDbSettings
begin
DbSettings := TDbSettings.Create;
try
// show some settings
WriteLn(DbSettings.Host);
WriteLn(DbSettings.Port);
// write setting
DbSettings.UserName := 'Me';
// store it in the ini file
DbSettings.Save;
finally
DbSettings.Free;
end;
end;
If you want to specify a new set of settings, it's really easy.
TServiceSettings=class(TAppSettings)
public
[IniValue('Service','Description','MyServiceDesc')]
ServiceDescription: String;
[IniValue('Service','DisplayName','MyServiceName')]
ServiceDisplayName: String;
end;
This is so much cleaner than directly reading and writing an inifile. Robert, if you read this: thanks for making my life much easier!
Here's the updated code:
unit WvN.Configuration.Persist.Ini;
// MIT License
//
// Copyright (c) 2009 - Robert Love
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE
//
// Wouter van Nifterick: 2010-11: added TSettings abstract class and some derivatives to load database and cs settings
interface
uses SysUtils,Classes, Rtti,TypInfo;
type
IniValueAttribute = class(TCustomAttribute)
private
FName: string;
FDefaultValue: string;
FSection: string;
public
constructor Create(const aSection : String;const aName : string;const aDefaultValue : Integer = 0);overload;
constructor Create(const aSection : String;const aName : string;const aDefaultValue : Double = 0.0);overload;
constructor Create(const aSection : String;const aName : string;const aDefaultValue : Boolean = false);overload;
constructor Create(const aSection : String;const aName : string;const aDefaultValue : String = '');overload;
property Section : string read FSection write FSection;
property Name : string read FName write FName;
property DefaultValue : string read FDefaultValue write FDefaultValue;
end;
EIniPersist = class(Exception);
TIniPersist = class (TObject)
private
class procedure SetValue(aData : String;var aValue : TValue);
class function GetValue(var aValue : TValue) : String;
class function GetIniAttribute(Obj : TRttiObject) : IniValueAttribute;
public
class procedure Load(FileName : String;obj : TObject);
class procedure Save(FileName : String;obj : TObject);
end;
TSettings=class abstract(TComponent)
private
FOnChange: TNotifyEvent;
FFileName:String;
procedure SetOnChange(const Value: TNotifyEvent);
function GetFileName: String;virtual;
procedure SetFileName(const Value: String);virtual;
public
property FileName:String read GetFileName write SetFileName;
procedure CreateDefaults;
procedure Load;virtual;
procedure Save;virtual;
constructor Create(AOwner: TComponent); override;
procedure DoOnChange;
property OnChange:TNotifyEvent read FOnChange write SetOnChange;
end;
TAppSettings=class abstract(TSettings)
function GetFileName: String;override;
end;
TServiceSettings=class(TAppSettings)
public
[IniValue('Service','Description','')]
ServiceDescription: String;
[IniValue('Service','DisplayName','')]
ServiceDisplayName: String;
end;
TCsSettings=class(TAppSettings)
public
[IniValue('CS','SourceAppId',9999)]
SourceAppId: LongWord;
[IniValue('CS','SourceCSId',9999)]
SourceCSId: LongWord;
[IniValue('CS','Host','Localhost')]
Host: String;
[IniValue('CS','Port',42000)]
Port: LongWord;
[IniValue('CS','ReconnectInvervalMs',30000)]
ReconnectInvervalMs: Integer;
end;
TFTPSettings=class(TAppSettings)
public
[IniValue('FTP','Host','Localhost')]
Host: String;
[IniValue('FTP','Port',21)]
Port: LongWord;
[IniValue('FTP','RemotePath','/')]
RemotePath: String;
[IniValue('FTP','LocalPath','.')]
LocalPath: String;
[IniValue('FTP','Username','')]
Username: String;
[IniValue('FTP','Password','')]
Password: String;
[IniValue('FTP','BlockSize',4096)]
BlockSize: Cardinal;
end;
TDbSettings=class(TAppSettings)
private
function GetURL: String;
public
[IniValue('DB','Host','Localhost')]
Host: String;
[IniValue('DB','Port',3306)]
Port: LongWord;
[IniValue('DB','Database','')]
Database: String;
[IniValue('DB','Username','root')]
Username: String;
[IniValue('DB','Password','')]
Password: String;
[IniValue('DB','Protocol','mysql-5')]
Protocol: String;
[IniValue('DB','UseSSL',True)]
UseSSL: Boolean;
[IniValue('DB','Compress',True)]
Compress: Boolean;
[IniValue('DB','TimeOutSec',0)]
TimeOutSec: Integer;
[IniValue('DB','SSL_CA','U:\Efkon2\AMM_mysql_cas.crt')]
SSL_CA: String;
[IniValue('DB','SSL_CERT','U:\Efkon2\AMM_ARS_mysql_user.pem')]
SSL_CERT: String;
[IniValue('DB','SSL_KEY','U:\Efkon2\AMM_ARS_mysql_user_key.pem')]
SSL_KEY: String;
property URL:String read GetURL;
end;
TPathSettings=class(TAppSettings)
public
[IniValue('Paths','StartPath','.')]
StartPath: String;
[IniValue('Paths','InPath','In')]
InPath: String;
[IniValue('Paths','OutPath','Out')]
OutPath: String;
[IniValue('Paths','ErrorPath','Error')]
ErrorPath: String;
end;
implementation
uses IniFiles;
{ TIniValue }
constructor IniValueAttribute.Create(const aSection, aName, aDefaultValue: String);
begin
FSection := aSection;
FName := aName;
FDefaultValue := aDefaultValue;
end;
{ TIniPersist }
class function TIniPersist.GetIniAttribute(Obj: TRttiObject): IniValueAttribute;
var
Attr: TCustomAttribute;
begin
for Attr in Obj.GetAttributes do
begin
if Attr is IniValueAttribute then
begin
exit(IniValueAttribute(Attr));
end;
end;
result := nil;
end;
class procedure TIniPersist.Load(FileName: String; obj: TObject);
var
ctx : TRttiContext;
objType : TRttiType;
Field : TRttiField;
Prop : TRttiProperty;
Value : TValue;
IniValue: IniValueAttribute;
Ini : TIniFile;
Data : string;
begin
ctx := TRttiContext.Create;
try
Ini := TIniFile.Create(FileName);
try
objType := ctx.GetType(Obj.ClassInfo);
for Prop in objType.GetProperties do
begin
IniValue := GetIniAttribute(Prop);
if Assigned(IniValue) then
begin
Data := Ini.ReadString(IniValue.Section, IniValue.Name, IniValue.DefaultValue);
Value := Prop.GetValue(Obj);
SetValue(Data, Value);
Prop.SetValue(Obj, Value);
end;
end;
for Field in objType.GetFields do
begin
IniValue := GetIniAttribute(Field);
if Assigned(IniValue) then
begin
Data := Ini.ReadString(IniValue.Section, IniValue.Name, IniValue.DefaultValue);
Value := Field.GetValue(Obj);
SetValue(Data, Value);
Field.SetValue(Obj, Value);
end;
end;
finally
Ini.Free;
end;
finally
ctx.Free;
end;
end;
class procedure TIniPersist.SetValue(aData: String;var aValue: TValue);
var
I : Integer;
begin
case aValue.Kind of
tkWChar,
tkLString,
tkWString,
tkString,
tkChar,
tkUString : aValue := aData;
tkInteger,
tkInt64 : aValue := StrToInt(aData);
tkFloat : aValue := StrToFloat(aData);
tkEnumeration: aValue := TValue.FromOrdinal(aValue.TypeInfo,GetEnumValue(aValue.TypeInfo,aData));
tkSet: begin
i := StringToSet(aValue.TypeInfo,aData);
TValue.Make(#i, aValue.TypeInfo, aValue);
end;
else raise EIniPersist.Create('Type not Supported');
end;
end;
class procedure TIniPersist.Save(FileName: String; obj: TObject);
var
ctx : TRttiContext;
objType : TRttiType;
Field : TRttiField;
Prop : TRttiProperty;
Value : TValue;
IniValue: IniValueAttribute;
Ini : TIniFile;
Data : string;
begin
ctx := TRttiContext.Create;
try
Ini := TIniFile.Create(FileName);
try
objType := ctx.GetType(Obj.ClassInfo);
for Prop in objType.GetProperties do
begin
IniValue := GetIniAttribute(Prop);
if Assigned(IniValue) then
begin
Value := Prop.GetValue(Obj);
Data := GetValue(Value);
Ini.WriteString(IniValue.Section, IniValue.Name, Data);
end;
end;
for Field in objType.GetFields do
begin
IniValue := GetIniAttribute(Field);
if Assigned(IniValue) then
begin
Value := Field.GetValue(Obj);
Data := GetValue(Value);
Ini.WriteString(IniValue.Section, IniValue.Name, Data);
end;
end;
finally
Ini.Free;
end;
finally
ctx.Free;
end;
end;
class function TIniPersist.GetValue(var aValue: TValue): string;
begin
if aValue.Kind in [tkWChar, tkLString, tkWString, tkString, tkChar, tkUString,
tkInteger, tkInt64, tkFloat, tkEnumeration, tkSet] then
result := aValue.ToString
else
raise EIniPersist.Create('Type not Supported');
end;
constructor IniValueAttribute.Create(const aSection, aName: string;
const aDefaultValue: Integer);
begin
FSection := aSection;
FName := aName;
FDefaultValue := IntToStr(aDefaultValue);
end;
constructor IniValueAttribute.Create(const aSection, aName: string;
const aDefaultValue: Double);
begin
FSection := aSection;
FName := aName;
FDefaultValue := FloatToStr(aDefaultValue);
end;
constructor IniValueAttribute.Create(const aSection, aName: string;
const aDefaultValue: Boolean);
begin
FSection := aSection;
FName := aName;
FDefaultValue := BoolToStr(aDefaultValue);
end;
{ TAppSettings }
procedure TSettings.CreateDefaults;
begin
Load;
Save;
end;
procedure TSettings.DoOnChange;
begin
if Assigned(FOnChange) then
FOnChange(Self)
end;
procedure TSettings.SetOnChange(const Value: TNotifyEvent);
begin
FOnChange := Value;
end;
{ TAppSettings }
function TAppSettings.GetFileName: String;
begin
Result := ChangeFileExt(ParamStr(0),'.config.ini')
end;
{ TSettings }
constructor TSettings.Create(AOwner: TComponent);
begin
inherited;
end;
function TSettings.GetFileName: String;
begin
Result := FFileName
end;
procedure TSettings.Load;
begin
TIniPersist.Load(FileName,Self);
DoOnChange;
end;
procedure TSettings.Save;
begin
TIniPersist.Save(FileName,Self);
end;
procedure TSettings.SetFileName(const Value: String);
begin
FFileName := Value
end;
{ TDbSettings }
function TDbSettings.GetURL: String;
begin
Result := Format('%s://%s:%s#%s:%d/%s?compress=%s&timeout=%d',
[
self.Protocol,
self.Username,
self.Password,
self.Host,
self.Port,
self.Database,
booltostr(self.Compress),
self.TimeOutSec
]);
end;
end.