"Object Aware" GUI Controls

You have several patterns for linking ORM with User Interface.

See for instance the Model GUI Mediator pattern. In short, you write an observer which will reflect the ORM content into the UI components, and vice versa. This has been implemented for instance in the tiOpf framework for Delphi (this link has videos).

Another approach is to map your data at runtime: you design your form just like usual, then you fill the content in the OnShow event, then the "Save" or "OK" button will validate then save the content into the ORM record. This is what is done in the main Sample application of our framework. Easy to code in this simple sample, but could lead into spaghetti code if you have a lot of fields and validation to operate.

The last approach is to let your ORM creates the form.

In our framework, you can define some UI properties about each table, in a dedicated structure. Then a single unit will create a form with all editable fields of your ORM object. Links to other records will be displayed as a combo box, booleans as checkboxes, sets as radioboxes, and so on. Then the filtering (e.g. trim a text field from spaces on left or right side) and the validation (e.g. ensure that a field value is unique or a valid IP address) is handled not in the UI part, but in the business logic itself, i.e. the ORM.

IMHO it's mandatory to keep a true multi-tier architecture. That is, the UI has to rely mostly on the business logic. For instance, data validation must be part of the ORM, not of the UI. For instance, if you decide to add a web client to your Delphi client application, you won't have to code the validation another time: it will be common to both clients, separated from the UI implementation details.


What you could do (though I have no code samples) is use a combination of

  • class helpers or interceptor classes
  • binding interfaces for single domain objects and/or domain object lists

Class helpers have the disadvantage that they are not officially supported and you cannot add any fields to the class you are helping.

Interceptor classes are simply descendant classes with the same name as their ancestor:

uses
  stdctrls;

type
  TButton = class(stdctrls.TButton)
  end;

You can put interceptor classes in their own unit and use that whereever you want. Just make sure these units are included AFTER the standard unit, so your descendant is used at run time.

Benefit of interceptor classes:

  • You can continue to design your UI using standard VCL or third party controls.
  • You get all the advantages of descendants.
  • You do not need to create or install your own controls.
  • No need for special mapper classes or use of RTTI.
  • Easily (well, relatively easily) integrated into a (DUnit-) testable user interface along the lines of Julian Bucknall's article on this in the (distinct) Delphi Magazine as referred to in this question/answer: Unit-testing mouse event handlers

Pseudo sample of interceptor control with binding interface / command interface:

uses
  stdctrls;

type
  ICommandAction = interface(IInterface)
    function IsEnabled: Boolean;
    procedure Execute;
    procedure Update;
  end;

  IBindSingle = interface(IInterface)
    function GetValueFromControl: string;
    procedure LoadValueIntoControl(const aValue: string);
  end;

  TButton = class(stdctrls.TButton, ICommandAction)
  protected
    function IsEnabled: Boolean;
    procedure Execute;
    procedure Update;
  end;

  TEdit = class(stdctrls.TEdit, IBindSingle)
    function GetValueFromControl: string;
    procedure LoadValueIntoControl(const aValue: string);
  end;

implementation could be along the lines of:

  function TButton.IsEnabled: Boolean;
  begin
    Result := Self.Enabled;
  end;

  procedure TButton.Execute;
  begin
    Self.Action.Execute;
  end;

  procedure TButton.Update;
  begin
    Self.Action.Update;
  end;

  function TEdit.GetValueFromControl: string;
  begin
    Result := Self.Text;
  end;

  procedure LoadValueIntoControl(const aValue: string);
  begin
    Self.Text := aValue;
  end;