Simplify your Delphi Code using some OO techniques (Part 4)
written by Stefaan Lesage on 30/03/2010- posted in Software Development Windows Delphi
- 4 comments
- archive
Part 4 in the series. Actually it's the rest of Part 3, since for some strange reason I couldn't get it into a single post.
Continued
Since for some strange reason I couldn't fit everything in a single post, I had to split it up, so this is the rest of it
The TdvStringSetting class
The explanation
Basically the TdvSetting provides us with a skeleton we can use for our new Setting descendants. I ended up with a TdvStringSetting, TdvIntegerSetting, TdvBooleanSetting and quite a few others, but lets start with the TdvStringSetting first.
The Code
TdvStringSetting = class( TdvSetting )
private
function GetDefaultValueAsString: String;
protected
function GetAsBoolean: Boolean; override;
function GetAsDateTime: TDateTime; override;
function GetAsFloat: Double; override;
function GetAsInteger: Longint; override;
function GetAsString: string; override;
function GetAsVariant: Variant; override;
function GetValue(var Value: string): Boolean;
procedure SetAsBoolean(const Value: Boolean); override;
procedure SetAsDateTime(const Value: TDateTime); override;
procedure SetAsFloat(const Value: Double); override;
procedure SetAsInteger(const Value: Longint); override;
procedure SetAsString(const aValue: string); override;
procedure SetVarValue(const aValue: Variant); override;
public
procedure SaveToRegIni ( aRegIni : TRegistryIniFile; const aSection : String ); override;
procedure LoadFromRegIni( aRegIni : TRegistryIniFile; const aSection : String ); override;
property DefaultValue : String read GetDefaultValueAsString;
property Value : string read GetAsString
write SetAsString;
end;
...
{ TdvStringSetting }
function TdvStringSetting.GetAsBoolean: Boolean;
var
S: string;
begin
S := GetAsString;
Result := (Length(S) > 0) and (S[1] in ['T', 't', 'Y', 'y']);
end;
function TdvStringSetting.GetAsDateTime: TDateTime;
begin
Result := StrToDateTime(GetAsString);
end;
function TdvStringSetting.GetAsFloat: Double;
begin
Result := StrToFloat(GetAsString);
end;
function TdvStringSetting.GetAsInteger: Longint;
begin
Result := StrToInt(GetAsString);
end;
function TdvStringSetting.GetAsString: string;
begin
if not GetValue(Result) then Result := '';
end;
function TdvStringSetting.GetAsVariant: Variant;
var
S: string;
begin
if GetValue(S) then Result := S else Result := Null;
end;
function TdvStringSetting.GetDefaultValueAsString: String;
begin
Result := FDefaultValue;
end;
function TdvStringSetting.GetValue(var Value: string): Boolean;
begin
Value := FValue;
Result := True;
end;
procedure TdvStringSetting.LoadFromRegIni(aRegIni: TRegistryIniFile;
const aSection: String);
begin
inherited LoadFromRegIni( aRegIni, aSection );
Value := aRegIni.ReadString( aSection, Identifier, DefaultValue );
end;
procedure TdvStringSetting.SaveToRegIni(aRegIni: TRegistryIniFile;
const aSection: String);
begin
inherited SaveToRegIni( aRegIni, aSection );
aRegIni.WriteString( aSection, Identifier, Value );
end;
procedure TdvStringSetting.SetAsBoolean(const Value: Boolean);
const
Values: array[Boolean] of string[1] = ('F', 'T');
begin
SetAsString(Values[Value]);
end;
procedure TdvStringSetting.SetAsDateTime(const Value: TDateTime);
begin
SetAsString(DateTimeToStr(Value));
end;
procedure TdvStringSetting.SetAsFloat(const Value: Double);
begin
SetAsString(FloatToStr(Value));
end;
procedure TdvStringSetting.SetAsInteger(const Value: Integer);
begin
SetAsString(IntToStr(Value));
end;
procedure TdvStringSetting.SetAsString(const aValue: string);
begin
FValue := aValue;
end;
procedure TdvStringSetting.SetVarValue(const aValue: Variant);
begin
SetAsString(aValue);
end;
What does it do ?
Since we had the skeleton of the TdvSetting in place, the only thing we needed to do was to override the necessary methods and implement our own features. As you can see, we added approximately the same code as can be found in the TStringField in the VCL.
The only additional code is the implementation of the SaveToRegIni and LoadFromRegIni methods. These methods will actually load and save the value of the Setting to the Registry. Additionally when loading the value we will be using the Default value if the setting can't be found in the regsitry. The Section where we will read / write the value can be supplied to the method and we will be using the identifier (name) of the setting as the key
Creating the TdvSettings class
The explanation
Well, now that we can make different type of Setting objects, we also need some kind of container to hold them. For example our application might have quite a few settings : PrintInColor, CheckForUpdates, AutoConnect, ... but we will need some way to access them. As mentioned before I created the TdvSettings class as a simple TObjectList. It will hold a reference to the individual TdvSetting objects and we will be able to access them
The Code
TdvSettings = class( TObjectList )
private
FRootKey: String;
protected
procedure CreateSettings; virtual;
function GetItems(Index: Integer): TdvSetting;
procedure SetItems(Index: Integer; ASetting: TdvSetting);
public
constructor Create( const aRootKey : String );
function Add(ASetting: TdvSetting): Integer;
function Extract(Item: TdvSetting): TdvSetting;
function Remove(ASetting: TdvSetting): Integer;
function IndexOf(ASetting: TdvSetting): Integer;
function First: TdvSetting;
function Last: TdvSetting;
function SettingByIdentifier( const aIdentifier : String ) : TdvSetting;
procedure LoadFromRegistry;
procedure SaveToRegistry;
procedure Insert(Index: Integer; ASetting: TdvSetting);
property Items[Index: Integer]: TdvSetting read GetItems write SetItems; default;
property RootKey : String read FRootKey write FRootKey;
end;
...
{ TdvSettings }
function TdvSettings.Add(ASetting: TdvSetting): Integer;
begin
Result := inherited Add(ASetting);
end;
constructor TdvSettings.Create(const aRootKey: String);
begin
inherited Create( True );
FRootKey := aRootKey;
CreateSettings;
// Read the settings from the Registry once the Settings object list has
// been initialised.
LoadFromRegistry;
end;
procedure TdvSettings.CreateSettings;
begin
end;
function TdvSettings.Extract(Item: TdvSetting): TdvSetting;
begin
Result := TdvSetting(inherited Extract(Item));
end;
function TdvSettings.First: TdvSetting;
begin
Result := TdvSetting(inherited First);
end;
function TdvSettings.GetItems(Index: Integer): TdvSetting;
begin
Result := TdvSetting(inherited Items[Index]);
end;
function TdvSettings.IndexOf(ASetting: TdvSetting): Integer;
begin
Result := inherited IndexOf(aSetting);
end;
procedure TdvSettings.Insert(Index: Integer; ASetting: TdvSetting);
begin
inherited Insert(Index, aSetting);
end;
function TdvSettings.Last: TdvSetting;
begin
Result := TdvSetting(inherited Last);
end;
procedure TdvSettings.LoadFromRegistry;
var
lIndex : Integer;
lSetting : TdvSetting;
lRegIni : TRegistryIniFile;
begin
lRegIni := TRegistryIniFile.Create('');
try
for lIndex := 0 to Pred( Count ) do
begin
lSetting := Items[ lIndex ];
lSetting.LoadFromRegIni( lRegIni, RootKey );
end;
finally
FreeAndNil( lRegIni );
end;
end;
function TdvSettings.Remove(ASetting: TdvSetting): Integer;
begin
Result := inherited Remove(aSetting);
end;
procedure TdvSettings.SaveToRegistry;
var
lIndex : Integer;
lSetting : TdvSetting;
lRegIni : TRegistryIniFile;
begin
lRegIni := TRegistryIniFile.Create('');
try
for lIndex := 0 to Pred( Count ) do
begin
lSetting := Items[ lIndex ];
lSetting.SaveToRegIni( lRegIni, RootKey );
end;
finally
FreeAndNil( lRegIni );
end;
end;
procedure TdvSettings.SetItems(Index: Integer; ASetting: TdvSetting);
begin
inherited Items[Index] := aSetting;
end;
function TdvSettings.SettingByIdentifier(
const aIdentifier: String): TdvSetting;
var
lcv : Integer;
begin
Result := Nil;
for lcv := 0 to Pred( Count ) do
begin
if ( Items[ lcv ].Identifier = aIdentifier ) then
begin
Result := Items[ lcv ];
Break;
end;
end;
end;
What does it do ?
Well, it is simply a container for a number of TdvSetting objects. It has some basic code which will allow us to access the individual TdvSetting objects using their index or their Identifier (name). We will be able to set the RootKey of the TdvSettings object and that is the base key which will be used when loading / storing the individual TdvSetting objects.
You willl notice that I added a CreateSettings method which is currently empty. The goal is to implement this in descendant classes. At the TdvSettings level we don't yet know which individual settings we will need, how they are called and what type they are ... Yet, I wanted to be able to create those settings from within the base class and once that is done read the settings if necessary.
In my TdvMyApplicationSettings I will override this method and add the necessary code to set up the individual TdvSetting objects I need for my applicaiton.
What's next ?
Currently we have a basic skeleton in place and we are able to continue from here. If you're up for a challenge, you might want to create your own TdvSetting descendants. I noticed I needed one for Integer values, DataTime values and Boolean values, so you could try to implement those.
As I mentioned at the end of the previous article, this isn't the only way, nor THE way to solve this problem, and I actually did try a few other approaches as well. It did take me a few iterations to get to this stage. For example, initial versions had the actual code for creating the TRegistryIniFile class inside the individual TdvSetting objects.
Quickly I noticed though that I had the exact same code in all my TdvSetting descendants LoadFromRegIni and SaveToRegIni methods so I started thinking again. I thought that constructing & destroying the TRegistryIniFile might be something I don't want to execute for the 20 individual application settings so I ended up refactoring the code to it's current status
Feedback
I'm quite sure the code could be simplified and maybe improved, and I do welcome all suggestions and feedback. So feel free to post all comments on this blog and I'll try to respond ASAP. Maybe your feedback will shape the next article in this series ...
