Monday, November 17, 2014

Persistent Properties Without Hardcoding Part 1

Concepts: Properties, Array Properties, IniFiles, Run Time Type Information

Introduction

How many times have you sat down in the Delphi ide and code editor or in another specialized visual class editor and thought: Gee, if I encapsulated the process required to create properties, I could work a lot faster.

The truth is that you can encapsulate the process of creating properties for your form, component or any class that you'd like to be persistent. It only has a few requirements for your project or application.


  • Your application has file or registry access (preferably file access to its own directory).
  • Your application is not required to access these properties at high speed (as in a video game or graphics animation where accessing properties or fields might occur hundreds or even thousands of times per second).
  • The types you create are derived from simple types (ie not class or record types unless they are serializable into a simple type like a string).
We've given a hint that storage for the actual values for our properties will be kept in a file or the registry. That is precisely the case and in the case of using a file, specifically an Ini file, it allows us to make configuration changes before the GUI for your program is complete. So you can make all the important design tests without an interface at all because you can edit the state of properties from a text editor. The only GUI element you'll need is a means to signal a state change to the application itself if you've edited a property in such a manner. A button would do nicely for such purposes.

We will put all of the functionality for a property, just about any simple type based property in this property handler. In doing there will also be some unique advantages to this approach.

  • We will be able to defer the type specification until the property is needed, and not before.
  • No class specific code for property getters and setters. We can implement a single getter and setter to handle all properties that pass through the handler.
Array Properties

Array properties are a powerful language feature offered by Object Pascal and Delphi. They allow you to specify and index which is passed to a property getter or setter to handle the transaction.

ie

interface

TMyClass = class
private
  function GetIndexedProperty(Index: Integer): string;
  procedure SetIndexedProperty(Index: Integer; Const Value: string);
public
  property IndexedProperty[Index: Integer]: string read GetIndexedProperty write SetIndexedProperty;
end;

...

implementation

function TMyClass.GetIndexedProperty(Index: Integer): string;
begin
  // We don't have to get the value from an array.
  // We could retrieve it from a text file or from another object.
  Result := IntToStr(Index * 12);
end;

procedure TMyClass.GetIndexedProperty(Index: Integer; Const Value: string);
begin
  // We can do anything with Value that we want from here
  // There is no rule that says we have to store the value
  // or do anything with it
  MyArray[Index] := Value;
end;

In the above example, it is easy to see that we are using an Array Property much like we might in any application where the need arises.  When using Array Properties there is no rule that says that we have to use an Integer for the Index Specifier or even use the Index Specifier as the index into an array. It could be used for anything if we think a bit outside of the box. 

Take the following code as an example:


interface

TMyClass = class
private
  function GetIntegerProperty(Index: string): Integer;
  procedure SetIntegerProperty(Index: string; Const Value: Integer);
public
  property IntegerProperty[Index: Integer]: string read GetIntegerProperty write SetIntegerProperty;
end;

...

implementation

uses SysUtils, IniFiles;

function TMyClass.GetIntegerProperty(Index: string): Integer;
begin
  with TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini')) do
  try
    Result := ReadInteger('Main',Index,0);
  finally
    Free;
  end;
end;

procedure TMyClass.GetIntegerProperty(Index: string; Const Value: Integer);
begin
  with TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini')) do
  try
    Result := WriteInteger('Main',Index,Value);
  finally
    Free;
  end;
end;



Now in the example above, we've used the Index Specifier from the Array Property IntegerProperty to reference the ident parameter of a call to ReadInteger and WriteInteger, both are methods from the TIniFile class. In this case, we aren't using Index as a number at all but we are using it as an identifier. That means that our Index Specifier can act as a variable identifier. We could even define new properties at runtime using this mechanism as long as they were unique. However the program itself would only use such properties that it knew about at runtime, not the ones that you make up at runtime.

So take the following code using the above defined class:

...

const 
  PROPERTY_AGE  = 'Age';
  PROPERTY_YEAR = 'Year';

uses SysUtils, Dialogs;

procedure SomeProc;
begin
  with TMyClass.Create do
  try
    IntegerProperty[PROPERTY_AGE] := 47;
    IntegerProperty[PROPERTY_YEAR] := 2014;
    ShowMessage(Format('The property %s is %d',[PROPERTY_AGE,IntegerProperty[PROPERTY_AGE]]));
    ShowMessage(Format('The property %s is %d',[PROPERTY_YEAR,IntegerProperty[PROPERTY_YEAR]]));
  finally
    Free;
  end;
end;

So in this example, we`ve actually declared the property in the const section above deferring the property type until we need it. Its ambiguous so the only reason we need a type is so we know how to store it, and so we can do things to it like add and subtract (numeric types) or truncate and append it (string types). In other words the type isn`t really important until we need it at runtime and until we have to store it.

This will give you some clues into how we are going to achieve persistent properties from one place in our code, without the need to specify property declarations every time we need a new property that is based upon a simple type (ie Integer, Byte, Word, Single, Double, String etc). Using these simple types we can handle all ordinal types like sets.

In the next article, we'll start implementing our property manager class.

Until then, good coding.

Brian Joseph Johns

No comments:

Post a Comment