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.