| Delphi Clinic | C++Builder Gate | Training & Consultancy | Delphi Notes Weblog | Dr.Bob's Webshop |
| |||||||
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.
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.