Delphi Programming

and software in general.

Thursday, July 31, 2008

Anonymous methods - Variable Scope?

I am reading about all the Tiburón goodies with great pleasure, but I find myself uncertain of how some aspects of anonymous methods actually will work.

What variables that are in scope at the point of definition will be allowed used within the anonymous method, and which references should be avoided?

Can the method be defined without being assigned to a local variable, ie directly as a parameter to a call?

With reference to the articles on the TGridViewController, would it be possible to convert this (class method pointer)...

procedure TViewDirectory.DefineAttributes;
begin
CreateTextColumn('Name', GetFileName, 300);
CreateTimeColumn('Date', GetFileDate, 100, taCenter);
CreateIntegerColumn('Size', GetFileSize, 100, taRightJustify);
end;

// Note that Dir is a protected class property of TViewDirectory

function TViewDirectory.GetFileDate(const Row:Integer):TDateTime;
begin
Result := FileDateToDateTime(Dir.Entry[Row].Time);
end;

function TViewDirectory.GetFileName(const Row:Integer):String;
begin
Result := Dir.Entry[Row].Name;
end;

function TViewDirectory.GetFileSize(const Row:Integer):Integer;
begin
Result := Dir.Entry[Row].Size;
end;
to this? I.e. in-line declared anonymous methods

procedure TViewDirectory.DefineAttributes;
begin
CreateTextColumn(function (const row:integer):String;
begin
Result := Dir.Entry[row].Name;
end, 'Name', 300);

CreateTimeColumn(function (const row:integer):TDateTime;
begin
Result := FileDateToDateTime(Dir.Entry[row].Time);
end, 'Time', 100, taCenter);

CreateIntegerColumn(function (const row:integer):Integer
begin
Result := Dir.Entry[row].Size;
end, 'Size', 100, taRightJustify);
end;
Apart from in-parameter definition (and any related syntactical mistakes I might have made), the problem here would be the reference to Dir. Since Dir is a class property (or private variable), it seems possible that this would be quite safe.

What about local variables?
Is this legal?
procedure DefineAttributes;
var
Reg : TRegistry;
Key, Value : TStringList;
begin
Reg := TRegistry.Create;
key := TStringList.Create;
Value := TStringList.Create;
try
Reg.OpenKeyReadOnly('\Software\Somewhere');
Reg.GetKeyNames(Key);
Reg.GetValueNames(Value);

// we have ensured that GetRowCount returns the correct count

CreateTextColumn( function (const row:integer):string;
begin
Result := Key[row];
end, 'Key');

CreateTextColumn( function (const row:integer):string;
begin
Result := Value[Row];
end, 'Value');

finally
FreeAndNil(Value);
FreeAndNil(Key);
FreeAndNil(Reg);
end;
end;
I am really looking forward to learn more on this!

Monday, July 28, 2008

Reusable Grid View - part 3

Let's take a closer look at how to hijack a pristine TStringGrid and make it our playground without inheriting the grid directly. The VCL class is well designed for reuse and expose properties and event handlers that we easily can grab hold of. The OnDrawCell event is of particular interest to us. That's a good spot to redefine how the grid retrieve and render it's content.

You will be assimilated
This is pretty self explanatory, but what we basically do is hook the draw routine and set up the appropriate number of rows and columns, and their respective default widths.
procedure TGridViewController.Refresh;
var
ix : Integer;
begin
if not Assigned(Grid)
then Exit;

if not IsEmpty
then begin // Set Visitor's drawing handler, row and column count
Grid.Enabled := True;
Grid.OnDrawCell := DrawCell;
Grid.RowCount := RowCount + FixedRows;
Grid.ColCount := Count;
for ix := 0 to Count - 1 // Set column widths
do begin
Grid.ColWidths[ix] := (Objects[ix] as TGridViewColumn).Width;
end;
end
else begin // disable drawing handler, set rows/cols to 1 and fill in 1 "blank"
Grid.Enabled := False;
Grid.OnDrawCell := nil;
Grid.RowCount := 1;
Grid.ColCount := 1;
Grid.ColWidths[0] := 0;
Grid.Cells[0,0] := defaultEmptyCell;
end;
if Grid.RowCount > 1
then begin // Reset header and Row positions in case empty grid overrode them.
Grid.FixedRows := FixedRows;
Grid.Row := FixedRows;
end;
Grid.Invalidate; // ensure that the grid is refreshed
end;
OnDrawCell
So this is where all the action is... err... actually, not much happens here. Instead we delegate the actual rendering to the column instance.
procedure TGridViewController.DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
(Objects[aCol] as TGridViewColumn).DrawCell(Grid.Canvas, aRow, Rect, State);
end;
The column instance then again divide the draw into two sections. The outer DrawCell does basic housekeeping of canvas color settings and a little color trickery (a feature that snuck in while I was having fun - my bad).
procedure TGridViewColumn.DrawCell(Canvas: TCanvas; aRow: Integer; Rect: TRect;
State: TGridDrawState);
var
FG, ofg,
BG, obg : TColor;
begin
with Canvas
do begin
ofg:=Font.Color;
obg:=Brush.Color;

if (State = []) // Not focused/selected/etc
then begin
FG := ofg;
BG := obg;

if aRow > 0 // Not a title line
then begin
ForeColor(aRow, FG);
BackColor(aRow, BG);
end;

if ofg <> FG then Font.Color:=FG;
if obg <> BG then Brush.Color:=BG;
end;

DrawCellInner(Canvas, aRow - Controller.FixedRows, Rect, State); // The business happens here

if (State = [])
then begin
if ofg <> FG then Font.Color:=ofg;
if obg <> BG then Brush.Color:=obg;
end;
end;
end;
The actual content rendering happens in DrawCellInner. Note that we do an adjustment related to grid layout here. If you look at the line where DrawCellInner is called, we subtract FixedRows from aRow, ensuring that our data is zero offset based.
procedure TGridViewColumn.DrawCellInner(Canvas: TCanvas; aRow: Integer;
Rect: TRect; State: TGridDrawState);
var
CellText : String;
w : Integer; // String width
txtAdj : Integer; // old adjust mode
begin
if aRow < 0
then CellText := Title
else CellText := FormattedText(aRow);
with Canvas
do begin
case FAlign of
taCenter : begin
w:=TextWidth(CellText);
if (w>(Rect.Right-Rect.Left))
then w := 2
else w := Round(((Rect.Right - Rect.Left) - w) / 2);
TextRect(Rect, Rect.Left + w,Rect.Top + 1, CellText);
end;
taRightJustify : begin
txtAdj:=SetTextAlign(Handle, ta_Top or ta_Right);
TextRect(Rect, Rect.Right - 3,Rect.Top + 1, CellText);
SetTextAlign(Handle, txtAdj);
end;
else // taLeftJustify
TextRect(Rect, Rect.Left+2,Rect.Top+1, CellText);
end;
end;
end;
In the inner draw routine we first figure out if we are drawing the title row or a data row. If it is a data row, we call the TGridViewColumn.FormattedText method.
function TGridViewTextColumn.FormattedText(aRow: Integer): String;
begin
Result := GetTextMethod(aRow);
end;
Does GetTextMethod look familiar? It is the procedure variable which holds the method we implement in our descendant viewcontroller class and pass on when we set up the column. That is the method which actually retrieve the data from the underlying data structure as described in part 2 (GetNumber, GetNumberSquared).

The rest of DrawCellInner measures the retrieved text and ensure it is adjusted as desired and rendered within the cell region.

Magic Colors
Back to ForeColor and BackColor in the outer draw. This is the closest we come to bells and whistles. To make the grid look more sophisticated, I added support for overriding the colors as well as a very simple default rowshading algorithm.
procedure TGridViewColumn.ForeColor(aRow: Integer; var Color: TColor);
begin
if Assigned(FGetForeColorMethod)
then GetForeColorMethod(aRow, Color)
else if DefaultForeColor <> clNone
then Color := DefaultForeColor;
end;

procedure TGridViewColumn.BackColor(aRow: Integer; var Color: TColor);
begin
if Assigned(FGetBackColorMethod)
then GetBackColorMethod(aRow, Color)
else if DefaultBackColor <> clNone
then Color := DefaultBackColor;

if Assigned(FGetShadeColorMethod)
then GetShadeColorMethod(aRow, Color);
end;
Both these methods start in the same way. The default color have been retrieved from the grid itself in the outer draw. First we check if a color method have been plugged in, if not - we check if a column color override has been set.

For the background color routine, there is another feature - a pluggable shading method. The default row shader looks like this...
procedure TGridViewController.DefaultRowShader(const aRow:Integer; var Color:TColor);
begin
if ShadeRows and (not Odd(aRow))
then begin
Color := Graphics.ColorToRGB(Color); // Translate theme/system colors to RGB
if ((Color and $FFFFFF) > $888888) // Lazy man's luminosity adjustment
then Color := Color - $080808
else Color := Color + $080808;
end;
end;
Too much flexibility? Well, maybe I broke the KISS rule, but no rule without exceptions :) Now I can have static or procedural color adjustments per row as well as per column.

Next: Reusable Grid goes code complete and come with examples.

Stay tuned.

Writing reusable code - part 2

There are a few practices that will help you write solid, maintainable, reusable code. A lot of software practitioners of greater fame than mine have written about these before, but it doesn't hurt to repeat good advice.

Note that I in no way claim this to be the one and only true way to software development nirvana, but just some practices that work for me. Your own opinions and experiences on the topic will be greatly appreciated, so feel free to comment.

Keep It Simple, Stupid
Reusable code should be kept low-feature. It can be a challenge to avoid feature creep and dependency bloat, so make an effort to follow the KISS rule. The more sophisticated stuff you add, the more you will need to "fix" and not to forget: Test! - when inheriting from the class.

Design for extendability
When you design a reusable class, you really have to define the scope of what you want to achieve. Decide on the basic feature set, then evaluate the possible offspring to this class and conceivable additional features, but to adhere to the KISS rule - you should not add the features until you need them. Instead, spend a little time on evaluating the impact of the features and put in any necessary hooks and leave enough comments to allow you to rediscover why you put them in in the first place.

Later, if you absolutely have to add a feature and cannot add it in an inherited class - make sure you don't break existing behavior in class instancing. A common mistake is to change a default property value in the base class constructor, which some derived class assumed had a specific state or value.

...and on the point of assumptions...

Never Assume, Always Assert
I bet you never heard that one before! :) Use Asserts to check your assumptions. It will save you from a number of embarrassing moments of broken code. The purists probably want this done in unit testing, but it can help add clarity to your code if it is done in the actual library code. Think of it as design by contract.
procedure TSimpleClass.ActOn(aParameter:TSomeClass; aValue:Integer);
begin
// Ensure aParameter is assigned and of correct type
Assert(Assigned(aParameter) and (aParameter is TSomeClass));
// and that aValue is in legal range
Assert(aValue >= 0);
...
Using asserts like these will help you catch runtime problems faster. They also will remind you of what conditioning you need to do before you call the method, and what assumptions you can make within the routine itself.

Avoid Dependencies
When you start pulling in code from other libraries or classes, you should consider the level of dependency that you introduce. A simple extra nice-to-have attribute may come with a payload of extra code that hardly ever will be called. If possible, put the extra attribute in a separate descendant class in a separate unit. The less code you have to compile in, the better.

Embrace Polymorphism
When you create classes that are intended as foundation for descendant classes, you should consider how to best support polymorphic instancing.

For example - if you are writing a container class that will hold a polymorph collection of elements - you may want to have a virtual class function that return the default class type used to instance new elements in the container, instead of just instancing the base element class. Doing so will allow descendant classes make reliable assumptions about the safety of typecasting within the extended methods in the descendant container class.
unit BasicClass;

interface

type
TElementClass = class of TBaseElement;

TBaseElement = class(TObject)
end;

TBaseContainer = class(TObject)
class function ElementClass:TElementClass; virtual;
function ValidElement(anElement:TBaseElement):Boolean;
end;

implementation

{ TBaseContainer }

class function TBaseContainer.ElementClass: TElementClass;
begin
Result := TBaseElement;
end;

function TBaseContainer.ValidElement(anElement: TBaseElement): Boolean;
begin
Result := anElement is ElementClass;
end;

end.
"But..." - you will say; "you can just override methods in the descendant class for the same effect?". Yes, you can and it works well when you add new functionality, but what about functionality that already has been implemented in class higher in your inheritance tree?
unit AdvancedClass;

interface
uses
BasicClass;

type
TAdvancedElement = class (TBaseElement)
end;

TAdvancedContainer = class(TBaseContainer)
class function ElementClass:TElementClass; override;
end;

implementation

{ TAdvancedContainer }

class function TAdvancedContainer.ElementClass: TElementClass;
begin
Result := TAdvancedElement;
end;

end.
The base class won't have an understanding of it's descendants, so if you want it to be able to work with content from descendant classes, you would either have to override/redo the functionality or - do as above - allow the base class to work with their own child classes as well.
program ExtendableClasses;
{$APPTYPE CONSOLE}
uses
SysUtils,
BasicClass in 'BasicClass.pas',
AdvancedClass in 'AdvancedClass.pas';

procedure Main;
var
Element1, Element2 : TBaseElement;
Container1, Container2 : TBaseContainer;
begin
Container1 := TBaseContainer.Create;
Container2 := TAdvancedContainer.Create;

Element1 := Container1.ElementClass.Create;
Element2 := Container2.ElementClass.Create;

Writeln('E1: ', Element1.ClassName);
Writeln('E2: ', Element2.ClassName);

Writeln('E1 valid in C1: ', Container1.ValidElement(Element1));
Writeln('E2 valid in C1: ', Container1.ValidElement(Element2));

Writeln('E1 valid in C2: ', Container2.ValidElement(Element1));
Writeln('E2 valid in C2: ', Container2.ValidElement(Element2));
end;

begin
try
try
Main;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
finally
Write('Press Enter: ');
Readln;
end;
end.
Result

This particularly goes for dialogs and forms that use a polymorph class - make a virtual class function to enable overriding the class instancing. Avoid the rigidly defined TSomeInheritableClass.Create constructs and call the virtual class type function instead.


Note on constructors
When I finally "got" OOP as opposed to structured programming, it was hard to drop the structured way of thinking when it came to instancing. Most of my classes would only have a constructor that took a ton of parameters to save a few lines at the point of instancing that class.

While this might work well for monomorphic classes, it really will not be good for a class hierarchy, and it took me some time to get to the habit of having a parameterless Create. Today, I almost wish for a compiler hint if I should happen to write constructors that require parameters.


Hide detail with Layers
Think in APIs and layers and try to propagate only content between the layers, and not details that are specific to connectivity, storage, etc. A good layered design will allow you to rip out and replace parts of the architecture without massive rewrites.

When you design your APIs or layers, you should consider leaving space or hooks for piggybacking in more content in yet-to-be defined formats where appropriate.

Isolate Data from the GUI
The less your GUI know about your data, the better. Vise versa, the less your data knows about the GUI, the better. Use a mediating class that does the job of knowing both and how to tie them together. This will ease the job of making the data structures as well as the GUI reusable.

It can be particularly useful to tie your business logic to your data and avoid implementing biz.logic in the GUI presentation. Your data will be easier to move to a different presentation form when the rules are not hard-coded in some draw routine. You don't want to have duplication of biz.logic code between the display routines, the report routines, the export routines, etc.

-

Thats it for now. More on the topic of Layers and GUI abstraction to follow.
Also, part 3 of the reusable grid.

Wednesday, July 23, 2008

Reusable Grid View - part 2

The Controller

Our basic TGridViewController will hold a list of TGridViewColumns. I could make a simple array, or I could use TStringList. TStringList is probably one of the best and one of the worst classes in Delphi. So easy to use and so easy to abuse. In this case, I will only need to manage a handful of columns, instead of thousands of lines of text - so it will be ok.

On the abuse side: before you start filling TStringList - consider adjusting the capacity. Resizing the list is EXPENSIVE. Also - fill first, sort later.

Ok, lets take a look at the initial skeleton of the controller class:

TGridViewController = class (TStringList)
private
FGrid : TStringGrid;
function GetIsEmpty:Boolean;
function GetCurrentSelectionIndex: Integer;
protected
procedure DefineAttributes; virtual; abstract;
function GetRowCount: Integer; virtual; abstract;
procedure SetGrid(const Value: TStringGrid); virtual;
procedure DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
function MakeColumn(ColType : TGridViewColumnType; aTitle:String; aWidth:Integer; anAlign:TAlignment):TGridViewColumn;
public
constructor Create; virtual;
destructor Destroy; override;
procedure Refresh;
function CreateTextColumn(aTitle:String; aGetText:TGetTextMethod; aWidth:Integer = 64; anAlign:TAlignment = taLeftJustify):TGridViewTextColumn;
property CurrentSelectionIndex:Integer read GetCurrentSelectionIndex;
property Grid: TStringGrid read FGrid write SetGrid;
property IsEmpty: Boolean read GetIsEmpty;
property RowCount:Integer read GetRowCount;
end;


I won't go into the details on the TGridViewColumn yet, but note the two abstract methods in the controller: DefineAttributes and GetRowCount. To make a working grid, these are what we need to override.

The Grid property identifies the grid that we will control and DrawCell is the method we plug into the grid onDrawCell event. MakeColumn is called by the CreateTextColumn with the appropriate TGridColumnView descendant - hiding the interaction with our parent string list. I guess you already have figured out that IsEmpty simply check the number of rows.

A simple Example

Lets consider the simplest possible example: a grid showing rows with the numbers 1 to 5, and their values squared. Note that the unit is pretty ignorant about what a grid is.


unit DemoNumbersClass;

interface

uses
Classes, SysUtils, FDCGridViewController;

type
TViewNumbers = class(TGridViewController)
private
function GetNumber(const Row: Integer): String;
function GetNumberSquared(const Row: Integer): String;
protected
procedure DefineAttributes; override;
function GetRowCount: Integer; override;
end;

We define two columns, and refer each column to a data retrival method.

implementation

{ TViewNumbers }

procedure TViewNumbers.DefineAttributes;
begin
CreateTextColumn('Number', GetNumber);
CreateTextColumn('Squared', GetNumberSquared, 64, taRightJustify);
end;

function TViewNumbers.GetNumber(const Row: Integer): String;
begin
Result := IntToStr(Row);
end;

function TViewNumbers.GetNumberSquared(const Row: Integer): String;
begin
Result := IntToStr(SQR(Row));
end;

function TViewNumbers.GetRowCount: Integer;
begin
Result := 5;
end;

end.



Then we create a Form, drop a string grid on it, tweak some row size settings etc., and add the controller.


unit DemoFDCGridViewController;

interface

uses
Windows, Messages, SysUtils, Classes, Controls, Forms,
StdCtrls, Grids, FDCGridViewController, DemoNumbersClass;

type
TGridViewForm = class(TForm)
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
private
Numbers : TViewNumbers;
public
{ Public declarations }
end;

var
GridViewForm: TGridViewForm;

implementation

{$R *.dfm}

{ TGridViewForm }

procedure TGridViewForm.FormCreate(Sender: TObject);
begin
// Create our controller
Numbers := TViewNumbers.Create;
// and introduce the controller to the host grid
Numbers.Grid := StringGrid1;
end;

end.

And the result:

As you can see, there are a few tweaks that I haven't described yet. Also, I still haven't revealed the simple steps done to invade our host grid.

Stay tuned :)

Source Code highlighting in Blogger

Oddly enough, Blogger don't come with any tools to syntax highlight source code. A little web spelunking unearthed a nifty little java-script (syntaxhighlighter by Alex Gorbatchev) that support multiple languages (C++, C#, CSS, Delphi, JS, Java, PHP, Python, Ruby, SQL, VB, XML, HTML). It is fairly easy to add it to your Blogger template, although you need a place to host the scripts and styles.

When the scripts are in the template, all you need to put in Blogger is:

<pre name="code" class="delphi">
program Demo;
begin
// Say hi
Writeln('Hello World');
end.
</pre>

... and it shows up as:

program Demo;
begin
// Say hi
Writeln('Hello World');
end.

Tuesday, July 22, 2008

Vacation Pattern

1. Enter Car
2. Travel to far off region
3. Take pictures
4. Buy T-Shirt
5. Return home
6. Upload pictures
7. Write blog entry and refer to pictures
8. Apologize for off-topic blog post
9. Promise new on-topic article real-soon-now

Wednesday, July 9, 2008

Writing Reusable Code

A couple of years back, I set up a homepage with some forum software and tried to do some lightweight blogging - but - unfortunately the spammers really loved this free publishing mechanism and filled up my forum with trash. I ended up shutting down that project.

But, as the saying goes - publish or perish - so here I go again but this time in a hopefully better protected environment.

This is the first article in what I hope will be a series on creating lightweight reusable code, and although I have no ambitions about making this a widespread toolkit, I hope it may be of use to someone.
It certainly is of use to me :)

Reusable Grid View - part 1

Needful Things

Did you ever write your data to a logfile to check them out? Maybe you wrote them to a string list and showed them in a Memo? Maybe you even stuffed them into a TStringGrid and found out the hard way how expensive that can be if you have a lot of data and how poorly the numeric data display as a string. You often end up having to tweak and re-tweak the formatting to make the dump readable.

After having to implent similar gridviews in three different tasks back to back and seeing more on the horizon, I decided that I wanted to make it easier to quickly put together a gridview for a specific set of data.

There are a lot of nice custom grid controls out there, but they all come at a price - either in the form of purchase, or in the form of having to invest time in writing glue to get your data in there, or even with cost of pure payload. Some grids are so function rich that they almost are a system in their own right.

Totally ignoring the not-invented-here syndrome warning lights, I decided to make my own little quick grid viewing kit.


Design goals

  • Make it ignorant to the underlying data structure
  • Avoid having to adapt your data structure to this specific viewer
  • Avoid duplicating data
  • Make it easy to configure
  • Make it easy to extend and modify

Design tradeoffs

  • The underlying data must have a known number of rows
  • Each column will be have one type of content
  • Make it reasonably stupid
    Bells: No
    Whistles: No
    Nice to haves: Well, maybe a few :)

Isolating the data and the viewer

In a way, the top three goals are in conflict. How can you access content if you don't know the format of the content? If you don't know the format, don't you have to convert it somehow, and don't you have to store that conversion? If you don't adapt the data to the viewer, how can you make a generic viewer?

Those that work with databases and web probably already know the answer: Base the solution on the Model View Controller pattern.

We need to find an effective way to encapsulate the data and present that encapsulation to the grid control. Let's briefly look at the options for encapsulating the data here.

  • Inheritance - Not good - I don't want to force our data to be based on a type specific to my viewer
  • Aggregation - I can make a class that knows both the dataset and the viewer

Aggregation allows me to write a base class that incorporate all the knowledge about the grid side of the problem, leaving only the need to implement a reasonably thin data access layer.

Since I don't want to duplicate content into the grid, I need to find a good way to retrieve what I need on demand and to massage it into the format I need to display it on the fly. The solution is to use something along the lines of a Visitor pattern. Since grid views typically have static columns - ie all fields in a column is of the same type - I will make the visitor look at the data from a column perspective, identifying which row it is visiting. This will be the column visitor (aka TGridViewColumn). I will create different type of grid view columns for different type of data. String, boolean, integer, float, date, and even an image one - which basically is just the integer visitor with a custom draw.

The Grid Controller

To avoid having to recreate the grid from scratch, I am going to attach myself to a TStringGrid. I am not going to inherit from it, just wrap it and inject myself where it counts. Why no inheritance? Well - I might want to move this code to a more capable grid at a later point in time. Also, by wrapping it - I can attach myself to a standard grid that is dropped in at design time without having to make a new component. This will be my grid controller (aka TGridViewController).

Custom Drawing

My key point for doing all this is to be able to display the data as best as I can. Hence, I will replace the TStringGrid cell drawing routine with my own. Firstly, I need a new string drawing routine. I'm going to add center and right justify, so that it will be easy to convert a number to a string, and then draw the string right justified. Fortunately, TStringGrid.OnDrawCell allows me to do inject my custom draw without severe tampering with TStringGrid.

I wasn't going to put in a lot of bells and whistles in my reusable grid view, but one thing that I thought would be useful was the abilty to control row color and column color. If I am viewing some sort of log - it would be nice if I could highlight a row with a problem, or a row matching some sort of highlight criteria. Again - I want to give the controller the same ability to tie together the underlying data and the color changes, so I will use the same visitor model for retrieving the customized color.

I am splitting my custom draw routine between the grid controller and the column visitor. This allow me to match the drawing customisation to the data access and conversion, both from a row as well as a column perspective. The column draw routine is actually split in a generic housekeeping outer draw routine, and a specific detail inner draw routine.

Property Getter

Initially I thought I would write descendant classes for each GridViewColumn per implementation. I.e. for each new view, I would write a class per column, inheriting the class from the appropriate column type (string, mumber etc). But then I realized that would mean quite a few extra lines of code to set up a grid.

So- how about using a pluggable visitor routine instead? That way I could actually implement the column data type column only once - and only add a single visitor routine in the custom GridViewController to retrive the actual value that is to be translated and drawn. The extra call overhead isn't really signficant since we are not typically talking several thousands of calls, but usually just a few hundred. Hence the GridViewColumn now has it's own property which hold the function used to retrieve the cell value from the GridViewController.

type
TGetTextMethod = function(const row:Integer):String of object;
TGetDoubleMethod = function(const row:Integer):Double of object;

That's all for the first part. More to come.

Proper database configuration is essential

...but not always obvious? Fabulous read at http://thedailywtf.com/Articles/Hastening-an-Inevitable.aspx about how to improve performance by simple means :)

Tuesday, July 1, 2008

Asking the right questions: What if...

To me, software development is a continuous search for answers...

  • What is the problem?
  • Is that really the problem?
  • What is the preferred solution?
  • How should it work?
  • How must it work?
  • How do we make that happen?
  • How should we break down the tasks?
  • How long will it take?
  • What will it cost?
  • Is it worth it?
  • Is there a plan B?

When you finally get down to the core programming task, again it boils down to asking the same question over and over:

What if ... ?

There are no stupid questions.