Simplify your Delphi Code using some basic rules, OO techniques and some refactoring (Part 3)

  

Now that we have a basic idea of what we want to achieve and how we could do it, it’s time to write some code and create some classes.Introduction

So, basically we need a class / object which we can use to read and write some application settings from and to the Windows Registry. Sounds pretty straightforward … but we did some additional thinking and found out that we might need to add some stuff in the future.

Basic Requirements of the code

Compatibility with Delphi 7

Although quite a few new features were added to the language in the last years, we won’t be using them ‘yet’. Our goal is that the code should compile / work under Delphi 7.

You may be asking yourselves “Who going though all that trouble for an old Delphi version ?”, well I noticed that even today some of my clients still have older projects which get compiled in Delphi 7. In later articles I might show you how to do it using some newer techniques, but for now lets just stick with something that will compile under Delphi 7

Not limited to the Windows Registry

Although we will be writing the code based on using the Windows Registry to Load and Save our data. We want an easy way to adapt our code for some other things like storing the data in XML or an Ini file. After all, we don’t know what the future will bring yet. We might be able to build applications for Windows Mobile, the Mac, iPhone or even iPad in the future (Would that be nice), and the Windows Registry might not be available on those devices.

For now we will focus on the Windows Registry, but as you notice it is a good idea to keep a few other possibilities ready. In the end, the only thing we care about is that we can store / load some settings. How or where these settings are stored isn’t all that important, it just needs to get done !

Additional Things

So far, we know we will need something to hold a set of settings. We need to be able to load and save those settings. We will probably be supplying a name for each individual setting, maybe even a default value and a description. We need to be able to store Integers, Strings, but who knows even Passwords, Dates, …

Lets get Coding !

… well almost …
Well, … actually before we start coding we could take a look at how some of these things get solved in the VCL. Of course, we could do all the coding ourselves, but it might be a good idea to let our new classes inherit from some existing classes. Since we need a list of things, you might want to take a look at the TList for example.

In my case, I knew I want to have a Setting which I will use to store Strings, one to store Integers and another one to store Booleans. Once I had those, I quickly noticed I wanted some others for DateTime values and quite a few other things as well. I actually ended up doing something quite similar to TField and TIntegerField, TStringField, …

So, now that I know I will be using different types of Setting objects and I want to keep a list of those Setting Objects, I quickly decided that the TObjectList was a very good class to start with.

Creating the TdvSetting class

The explanation

Basically I need an object with a few properties like an Identifier (or Name, Caption), a Description (or Hint) and of course a Value. I will need to be able to read the value from the Registry and write it to the registry. Additionally when reading the value from the Registry I want to check if there is already something in the registry for the setting, and if nothing is found, the Default value should be used.

Just as with the TField and TStringField, I want to be able to get the Value of the TdvSetting as a String or as a Variant, so I added that code as well. Additionally I want to set the value of the TdvSetting as well. In the end, the descendant classes will implement most of this, but like with the TField in the VCL, I added some code which will raise an exception if a descendant class doens’t implement a specific accessor.

This might sound a bit complex, but lets compare it with the TField and TStringField again. With a TStringField, you can set the value using aField.Value := theValue or with aField.AsString := theValue. Both things will work, but if aField is in instance of TField instead of TStringField an exception will be raised. What I did was implement that functionality as well.

For now we will focus on the Windows Registry, but as you notice it is a good idea to keep a few other possibilities ready. In the end, the only thing we care about is that we can store / load some settings. How or where these settings are stored isn’t all that important, it just needs to get done !

The Code

TdvSetting = class( TObject )
private
FValue : Variant;
FDefaultValue : Variant;
FIdentifier : String;
FCaption : String;
procedure SetCaption(const Value: String);
procedure SetVisible(const Value: Boolean);
protected
function GetAsBoolean: Boolean; virtual;
function GetAsDateTime: TDateTime; virtual;
function GetAsFloat: Double; virtual;
function GetAsInteger: Longint; virtual;
function GetAsString: string; virtual;
function GetAsVariant: Variant; virtual;

procedure SetAsBoolean(const Value: Boolean); virtual;
procedure SetAsDateTime(const Value: TDateTime); virtual;
procedure SetHint(const Value: String);
procedure SetAsFloat(const Value: Double); virtual;
procedure SetIdentifier(const Value: String);
procedure SetAsInteger(const Value: Longint); virtual;
procedure SetAsString(const Value: string); virtual;
procedure SetAsVariant(const Value: Variant); virtual;
protected
function AccessError(const TypeName: string): Exception; dynamic;
procedure SetVarValue( const Value : Variant ); virtual;
public
Constructor Create( const aIdentifier, aCaption : String;
const aDefaultValue : Variant ); virtual;

destructor Destroy; override;

procedure SaveToRegIni ( aRegIni : TRegistryIniFile;
const aSection : String ); virtual;
procedure LoadFromRegIni( aRegIni : TRegistryIniFile;
const aSection : String ); virtual;

procedure Clear; virtual;

property DefaultValue : Variant read FDefaultValue;
property AsBoolean : Boolean read GetAsBoolean
write SetAsBoolean;
property AsDateTime : TDateTime read GetAsDateTime
write SetAsDateTime;
property AsFloat : Double read GetAsFloat
write SetAsFloat;
property AsInteger : Longint read GetAsInteger
write SetAsInteger;
property AsString : string read GetAsString
write SetAsString;
property AsVariant : Variant read GetAsVariant
write SetAsVariant;

property Identifier : String read FIdentifier
write SetIdentifier;
property Caption : String read FCaption
write SetCaption;
property Value : Variant read GetAsVariant
write SetAsVariant;
end;

function TdvSetting.AccessError(const TypeName: string): Exception;
resourcestring
SSettingAccessError = ‘Cannot access Setting ”%s” (%s) as type %s’;
begin
Result := Exception.CreateResFmt( @SSettingAccessError,
[ Identifier, Caption, TypeName ] );
end;

procedure TdvSetting.Clear;
begin
FValue := Null;
end;

constructor TdvSetting.Create(const aIdentifier, aCaption: String;
const aDefaultValue: Variant);
begin
Create( aIdentifier, aCaption, aCaption, True, aDefaultValue );
end;

function TdvSetting.GetAsBoolean: Boolean;
begin
raise AccessError(‘Boolean’); { Do not localize }
end;

function TdvSetting.GetAsDateTime: TDateTime;
begin
raise AccessError(‘DateTime’); { Do not localize }
end;

function TdvSetting.GetAsFloat: Double;
begin
raise AccessError(‘Float’); { Do not localize }
end;

function TdvSetting.GetAsInteger: Longint;
begin
raise AccessError(‘Integer’); { Do not localize }
end;

function TdvSetting.GetAsString: string;
begin
Result := ClassName;
end;

function TdvSetting.GetAsVariant: Variant;
begin
raise AccessError(‘Variant’); { Do not localize }
end;

procedure TdvSetting.LoadFromRegIni(aRegIni: TRegistryIniFile;
const aSection: String);
begin
Assert( Assigned( aRegIni ), ‘The aRegIni parameter should contain a TRegIni Instance’ );
end;

procedure TdvSetting.SaveToRegIni(aRegIni: TRegistryIniFile;
const aSection: String);
begin
Assert( Assigned( aRegIni ), ‘The aRegIni parameter should contain a TRegIni Instance’ );
end;

procedure TdvSetting.SetAsBoolean(const Value: Boolean);
begin
raise AccessError(‘Boolean’); { Do not localize }
end;

procedure TdvSetting.SetAsDateTime(const Value: TDateTime);
begin
raise AccessError(‘DateTime’); { Do not localize }
end;

procedure TdvSetting.SetAsFloat(const Value: Double);
begin
raise AccessError(‘Float’); { Do not localize }
end;

procedure TdvSetting.SetAsInteger(const Value: Longint);
begin
raise AccessError(‘Integer’); { Do not localize }
end;

procedure TdvSetting.SetAsString(const Value: string);
begin
raise AccessError(‘String’); { Do not localize }
end;

procedure TdvSetting.SetAsVariant(const Value: Variant);
begin
if ( VarIsNull( Value ) ) then
begin
Clear;
end
else
begin
SetVarValue( Value );
end;
end;

procedure TdvSetting.SetCaption(const Value: String);
begin
FCaption := Value;
end;

procedure TdvSetting.SetHint(const Value: String);
begin
FHint := Value;
end;

procedure TdvSetting.SetIdentifier(const Value: String);
begin
FIdentifier := Value;
end;

procedure TdvSetting.SetVarValue(const Value: Variant);
begin
raise AccessError(‘Variant’); { Do not localize }
end;

What does it do ?

Actually this piece of code doesn’t do all that much. It just provides us with a base class we can now use as an ancestor for our other classes. Basically we have some ErrorHandling and a skeleton for our specific Setting classes.

Continued in Part 4

For some reason I had problems fitting everything into one single post, so I had to split it up an two parts. Go ahead and read the rest in part 4.

Comments are closed.