Team OO Software Development

Thursday, April 20, 2006

Design Patterns: Observer with Aspect

Reference: http://groups.google.com/group/borland.public.delphi.oodesign/browse_frm/thread/d1195c4cddf0245f/d5a7d8849ed16c49?q=observer+%2Baspect&rnum=2#d5a7d8849ed16c49

IAspect = interface
[GUID]
function CompareTo(const Other: IAspect): Boolean;
function GetName: string;
end;

TAspect = class
private
fName: string;
protected
constructor Create(const Name: string); overload;
// IAspect
function CompareTo(const Other: IAspect): Boolean;
function GetName: string;
end;

implementation

constructor TAspect.Create(const Name: string);
begin
inherited Create;
fName := Name;
end;

function TAspect.CompareTo(const Other: IAspect): Boolean;
begin
Result := Other = self as IAspect;
if not Result then
Result := Other.GetName = fName;
end;

function TAspect.GetName: string;
begin
Result := fName;
end;

//////////////////////////////////////////

TListAspectEnum = (listAppend, listInsert, listDelete, listChanged,
listEmpty);

IListAspect = interface(IAspect)
[GUID]
function EnumValue: TListAspectEnum;
function GetIdx: Integer;
function GetItem: TObject;
procedure SetIdx(Value: Integer);
procedure SetItem(const Value: TObject);
end;

//////////////////////////

TListAspect = class(TAspect, IListAspect)
public
class function Append: IListAspect;
class function Changed: IListAspect;
class function Delete: IListAspect;
class function Empty: IListAspect;
class function Insert: IListAspect;
private
fEnumValue: TListAspectEnum;
fIdx: Integer;
fItem: TObject;
constructor Create(EnumValue: TListAspectEnum);
protected
// IListAspect
function EnumValue: TListAspectEnum;
function GetIdx: Integer;
function GetItem: TObject;
procedure SetIdx(Value: Integer);
procedure SetItem(const Value: TObject);
end;

implementation

constructor TListAspect.Create(EnumValue: TListAspectEnum);
begin
inherited Create(GetEnumName(TypeInfo(TListAspectEnum), Ord(EnumValue)));
fEnumValue := EnumValue;
end;

class function TListAspect.Append: IListAspect;
begin
Result := Create(listAppend);
end;

class function TListAspect.Changed: IListAspect;
begin
Result := Create(listChanged);
end;

class function TListAspect.Delete: IListAspect;
begin
Result := Create(listDelete);
end;

class function TListAspect.Empty: IListAspect;
begin
Result := Create(listEmpty);
end;

class function TListAspect.Insert: IListAspect;
begin
Result := Create(listInsert);
end;

function TListAspect.EnumValue: TListAspectEnum;
begin
Result := fEnumValue;
end;

function TListAspect.GetIdx: Integer;
begin
Result := fIdx;
end;

function TListAspect.GetItem: IInterface;
begin
Result := fItem;
end;

procedure TListAspect.SetIdx(Value: Integer);
begin
fIdx := Value;
end;

procedure TListAspect.SetItem(const Value: TObject);
begin
fItem := Value;
end;

/////////////////////////////////////

This can then be used in the Aspect Observer; here are the definitions for
the extended classes

AspectObserver
procedure Update(const Subject: TObject; const Aspect: IAspect);

AspectSubject
procedure Attach(const Observer: AspectObserver);
procedure Detach(const Observer: AspectObserver);
procedure Notify(const Aspect: IAspect);

When using the ListAspect inside a List class, the syntax you get is
something like this :

procedure TListClass.InsertItem(Idx: Integer; const Item: TObject);
var
aspect: IListAspect;
begin
...
aspect := TListAspect.Insert;
aspect.SetIdx(Idx);
aspect.SetItem(Item);
fSubject.Notify(aspect);
...
end;

And in an Observer you would do something like this :

procedure TAnObserver.Update(const Subject: TObject; const Aspect: IAspect);
var
ListAspect: IListAspect
InsertedAt: Integer;
Item: TObject;
begin
if Supports(Aspect, IListAspect, ListAspect) then
begin
if ListAspect.CompareTo(TListAspect.Insert) then
begin
InsertedAt := ListAspect.GetIdx;
Item := ListAspect.GetItem;
...
end;
...
...

Things are a lot easier in .NET as enums can be declared internally to a
class and stored in a System.Enum field. You don't need to use interfaces as
you get true garbage collection of temporary objects. You can truly hide the
default TObject constructor so that folks cannot instantiate an Aspect apart
form using the named class methods.

Friday, April 07, 2006

GUI Design: Using List View for presentation in Delphi

To show a list of items with icons and text on a list. The first choice come into my mind is TListView. However, after doing some research on the VCL components, I found out there are alternative ways to do such presentation:
  1. TListView
  2. TcxListView
  3. TFlowPanel
  4. TcxGridCardView
  5. TScrollBox
Each component has their pros can cons. Here are the outcome of the research I have done:

TListView

Native Windows Control Wrapper VCL Component provide excellent upgrade and migration path in future. Use DoubleBuffered provide flicker free images when resize control during runtime. However, the TListView come with Delphi doesn't provide Tile view and Group View as seen on Windows XP windows explorer. To perform such view, please refer to :

1. Title: The ListView grouping feature (in Windows XP)
Author: Fabio Lucarelli
URL: http://www.smartr.easynet.be/articles/Listview_grouping_feature.htm

2. Title: The ListView “Tile” view feature
Author: Fabio Lucarelli
URL: http://www.smartr.easynet.be/articles/ListView_Tiles.htm

TcxListView by DevExpress

TcxListView is similar to TListView. However, it suffer from heavy flickering when resize form. The DoubleBuffered doesn't help at this point.

TFlowPanel

TFlowPanel provide good arragement on the control hosted on flow panel. However, the lack of scrolling support make it inappropriate here.

TcxGridCardView by DevExpress

TcxGridCardView is another component that able to provide similar functionanities as TListView. The strong canvas drawing events make the the card view able to draw graphics on the view:

procedure TForm1.cxGrid1CardView1CustomDrawCell(Sender: TcxCustomGridTableView;
ACanvas: TcxCanvas; AViewInfo: TcxGridTableDataCellViewInfo;
var ADone: Boolean);
var R: TRect;
B: TBitmap;
begin
if AViewInfo is TcxGridCardRowCaptionViewInfo then begin
R := AViewInfo.Bounds;
R.Top := R.Top + ImageList1.Height;
ACanvas.FillRect(R);

B := TBitmap.Create;
try
B.Height := FImage.Graphic.Height;
B.Width := FImage.Graphic.Width;
B.Canvas.Brush.Color := clWindow;
B.Canvas.FillRect(Rect(0, 0, B.Width, B.Height));
FImage.Graphic.Transparent := True;
B.Canvas.Draw(0, 0, FImage.Graphic);
B.Transparent := True;
ACanvas.Draw(AViewInfo.ContentBounds.Left, AViewInfo.ContentBounds.Top, B);
finally
B.Free;
end;

ADone := True;
end;
end;


TScrollBox

TScrollBox provide scrolling support but we have to arrange the items that involve lot of coding. It is not a wise choice.