Delphi Clinic C++Builder Gate Training & Consultancy Delphi Notes Weblog Dr.Bob's Webshop
Bob Swart (aka Dr.Bob) - Medical Officer Delphi 6 Developer's Guide
 Dr.Bob's Tip-Of-The-Hat #8
See Also: Delphi Papers, Columns and other Tip-Of-The-Hats

Right-Aligned Data-Aware TB42Edit
Two weeks ago, we implemented the Right-Aligned TB42Edit component, based on the TCustomMemo. Right after I published that tip, I received some feedback asking if it was also possible to get a data-aware version of this component (called TB42DBEdit). Well, yes it certainly is possible. Although you may try in more than one way, actually.
Before we start, however, you should realise that Delphi already has a way to align data-aware components, using persistent fields. For example, if a field called Table1Items is associated with a TIntegerField, then you can set the Alignment property (of the Table1Items persistent field property, not of the TDBEdit). So, although there is already a way to implement what has been asked, let's just see if we can make a TB42DBEdit that will always be right-aligned, no matter what...

First Attempt: derive from TDBEdit
Some people already tried by taking the TDBEdit component and looking for the Alignment property. There isn't an Alignment property, yet, but a FAlignment field does exist. Unfortunately, it's declared private (why?), so we can't use it (not even in a derived class), so we just have to redeclare it on our own:

  unit DBEdiTB42;
  interface
  uses
    Classes, DBCtrls;

  type
    TB42DBEdit = class(TDBEdit)
    private
      FAlignment: TAlignment;
    protected
      function GetAlignment: TAlignment;
      procedure SetAlignment(Value: TAlignment);
    published
      property Alignment: TAlignment read GetAlignment write SetAlignment;
    end;

  implementation

  function TB42DBEdit.GetAlignment: TAlignment;
  begin
    Result := FAlignment
  end;

  procedure TB42DBEdit.SetAlignment(Value: TAlignment);
  begin
    if FAlignment <> Value then
    begin
      FAlignment := Value;
      RecreateWnd
    end
  end;

  end.
Alas, like the TEdit component doesn't respond to the Alignment property (because it has an internal Alignment property, that responds to the Alignment property of the corresponding persistent field). So this is not a solution that will work (although we could try to hard-wire the Alignment property back to the persistent field again, but that's an experiment for another day)...

Second attempt: derive from TDBMemo
And although TDBMemo is based on a TMemo component, and hence reponds to the Alignment property, it has already published the Lines property, which is not what we'd want to see in our data-aware (single line) TB42DBEdit component. So this is not a possible solution either...

Third attempt: derive from TB42Edit
If we finally look inside the DBCtrls unit, we see that TDBMemo itself is derived from TCustomMemo (where TDBEdit is derived from TCustomMaskEdit - in other words: both the DB-version of the Memo and the Edit implement their own data-awareness). So, why not do this for TB42Edit as well? Just take TB42Edit as base class, and implement the data-awareness (which is based on a DataSource and FieldName property, delegated to a TDataFieldLink member):

  unit DBEdiTB42;
  interface
  uses
    Classes, Controls, DB, DBCtrls, EdiTB42;

  type
    TB42DBEdit = class(TB42Edit)
    private
      FFieldDataLink: TFieldDataLink;
      function GetDataField: String;
      function GetDataSource: TDataSource;
      procedure SetDataField(const Value: String);
      procedure SetDataSource(const Value: TDataSource);
      { Private declarations }
    protected
      { Protected declarations }
      procedure DataChange(Sender: TObject); // date changed in table
      procedure Change; override; // date changed by user in calendar
      procedure UpdateData(Sender: TObject); // change data in table
      procedure CmExit(var Message: TCmExit); message CM_Exit;
    public
      { Public declarations }
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
    published
      { Published declarations }
      property DataSource: TDataSource read GetDataSource write SetDataSource;
      property DataField: String read GetDataField write SetDataField;
    end;

  procedure Register;

  implementation

  procedure Register;
  begin
    RegisterComponents('DrBob42', [TB42DBEdit]);
  end;

  { TB42DBEdit }

  constructor TB42DBEdit.Create(AOwner: TComponent);
  begin
    inherited;
    FFieldDataLink := TFieldDataLink.Create;
    FFieldDataLink.OnDataChange := DataChange;
    FFieldDataLink.OnUpdateData := UpdateData
  end;

  destructor TB42DBEdit.Destroy;
  begin
    FFieldDataLink.Free;
    FFieldDataLink := nil;
    inherited
  end;

  function TB42DBEdit.GetDataField: String;
  begin
    Result := FFieldDataLink.FieldName
  end;

  function TB42DBEdit.GetDataSource: TDataSource;
  begin
    Result := FFieldDataLink.DataSource
  end;

  procedure TB42DBEdit.SetDataField(const Value: String);
  begin
    FFieldDataLink.FieldName := Value
  end;

  procedure TB42DBEdit.SetDataSource(const Value: TDataSource);
  begin
    FFieldDataLink.DataSource := Value
  end;

  procedure TB42DBEdit.DataChange(Sender: TObject);
  begin
    if Assigned(FFieldDataLink.Field) then
      Text := FFieldDataLink.Field.AsString
  end;

  procedure TB42DBEdit.Change;
  begin
    FFieldDataLink.Modified;
    inherited
  end;

  procedure TB42DBEdit.UpdateData(Sender: TObject);
  begin
    if Assigned(FFieldDataLink.Field) then
      FFieldDataLink.Field.AsString := Text
  end;

  procedure TB42DBEdit.CmExit(var Message: TCmExit);
  begin
    try
      FFieldDataLink.UpdateRecord
    except
      SetFocus;
      raise // re-raise exception
    end;
    inherited
  end;

  end.
And this will work as planned: the TB42DBEdit component is a right-aligned, data-aware editbox that can be used for numerical data entry, among others. Quite handy, if I may say so myself.
Remember that this new data-aware editbox no longer responds to the Alignment property of the associated persistent field, but I guess you already figured that out...

Epilogue
The fact that data-awareness needs to be implemented by every data-aware control leads me to believe that a solution based on the IDataAware interface is much more elegant. Please check out issue #68 of The Delphi Magazine for more details and a data-aware calendar using this IDataAware interface.


This webpage © 2001-2009 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.