I will answer this question myself, but feel free to provide your answers if you are faster than me or if you don\'t like my solution. I just came up with this idea and would li
I use XML for all my application as means of configuration. It is:
I have an XML library that makes it extremely easy to read or modify configuration, without even having to watch for missing values. Now you can also map the XML to a class inside application for faster access if speed is the issue, or certain values are read constantly.
I find other configuration methods far less optional:
Nicks answer (using Java Properties) has a point: this simple way to read and pass configuration around between parts of the application does not introduce dependencies on a special configuration class. A simple key/value list can reduce the dependencies between application modules and make code reuse easier.
In Delphi, a simple TStrings-based configuration is an easy way to implement a configuration. Example:
mail.smtp.host=192.168.10.8
mail.smtp.user=joe
mail.smtp.pass=*******
This would be for Java.
I like to use the java.util.Properties class for reading in config files or properties files. What I like is that you put your file with lines in the same way you showed above (key=value). Also, it uses a # (pound sign) for a line thats a comment, kind of like a lot of scripting languages.
So you could use:
ShowFlags=true
# this line is a comment
NumFlags=42
etc
Then you just have code like:
Properties props = new Properties();
props.load(new FileInputStream(PROPERTIES_FILENAME));
String value = props.getProperty("ShowFlags");
boolean showFlags = Boolean.parseBoolean(value);
Easy as that.
Basically you are asking for a solution to serialize a given object (in your case a configurations to ini files). There are ready made components for that and you can start looking here and here.
This is my proposed solution.
I have a base class
TConfiguration = class
protected
type
TCustomSaveMethod = function (Self : TObject; P : Pointer) : String;
TCustomLoadMethod = procedure (Self : TObject; const Str : String);
public
procedure Save (const FileName : String);
procedure Load (const FileName : String);
end;
The Load methods look like this (Save method accordingly):
procedure TConfiguration.Load (const FileName : String);
const
PropNotFound = '_PROP_NOT_FOUND_';
var
IniFile : TIniFile;
Count : Integer;
List : PPropList;
TypeName, PropName, InputString, MethodName : String;
LoadMethod : TCustomLoadMethod;
begin
IniFile := TIniFile.Create (FileName);
try
Count := GetPropList (Self.ClassInfo, tkProperties, nil) ;
GetMem (List, Count * SizeOf (PPropInfo)) ;
try
GetPropList (Self.ClassInfo, tkProperties, List);
for I := 0 to Count-1 do
begin
TypeName := String (List [I]^.PropType^.Name);
PropName := String (List [I]^.Name);
InputString := IniFile.ReadString ('Options', PropName, PropNotFound);
if (InputString = PropNotFound) then
Continue;
MethodName := 'Load' + TypeName;
LoadMethod := Self.MethodAddress (MethodName);
if not Assigned (LoadMethod) then
raise EConfigLoadError.Create ('No load method for custom type ' + TypeName);
LoadMethod (Self, InputString);
end;
finally
FreeMem (List, Count * SizeOf (PPropInfo));
end;
finally
FreeAndNil (IniFile);
end;
The base class could provide load and save methods for the delphi default types. I can then create a configuration for my application like this:
TMyConfiguration = class (TConfiguration)
...
published
function SaveTObject (P : Pointer) : String;
procedure LoadTObject (const Str : String);
published
property BoolOption : Boolean read FBoolOption write FBoolOption;
property ObjOption : TObject read FObjOption write FObjOption;
end;
Example of a custom save method:
function TMyConfiguration.SaveTObject (P : Pointer) : String;
var
Obj : TObject;
begin
Obj := TObject (P);
Result := Obj.ClassName; // does not make sense; only example;
end;
My preferred method is to create an interface in my global interfaces unit:
type
IConfiguration = interface
['{95F70366-19D4-4B45-AEB9-8E1B74697AEA}']
procedure SetConfigValue(const Section, Name,Value:String);
function GetConfigValue(const Section, Name:string):string;
end;
This interface is then "exposed" in my main form:
type
tMainForm = class(TForm,IConfiguration)
...
end;
Most of the time the actual implementation is not in the main form, its just a place holder and I use the implements keyword to redirect the interface to another object owned by the main form. The point of this is that the responsibility of configuration is delegated. Each unit doesn't care if the configuration is stored in a table, ini file, xml file, or even (gasp) the registry. What this DOES allow me to do in ANY unit which uses the global interfaces unit is make a call like the following:
var
Config : IConfiguration;
Value : string;
begin
if Supports(Application.MainForm,IConfiguration,Config) then
value := Config.GetConfiguration('section','name');
...
end;
All that is needed is adding FORMS and my global interfaces unit to the unit I'm working on. And because it doesn't USE the mainform, if I decide to later reuse this for another project, I don't have to do any further changes....it just works, even if the configuration storage scheme is completely different.
My general preference is to create a table (if I'm dealing with a database application) or an XML file. If it is a multi-user database application, then I will create two tables. One for global configuration, and another for user configuration.