?
八、數據敏感控件的制作。
Delphi的一大亮點就是它的數據庫開發能力。而數據敏感組件則在這中間起著很重要的作用。在Delphi的Data Control頁面下的控件都是用于顯示和編輯數據庫中的數據的。相信大家已經體會到數據敏感控件的好處了。我們這一節就給大家演示一下數據敏感控件的開發方法。
需要提醒大家的是,不像其他體系的控件,數據敏感控件并沒有一個統一的基類,只要是從TwinControl類或其子類派生就可以,數據敏感控件的特殊之處就在于我們下面提到的數據連接。
相信用Delphi開發過數據庫的人一定對delphi中沒有一個日期數據敏感控件而惱火。每次都要我們自己處理數據的更新與顯示。所以我們就來開發一個DBDateTimePicker控件。
新建一個控件,從TdateTimePicker派生,源代碼如下:
{*******************************************************}
{?????? Linco TDBDateTimePicker
{?????? mail me: about521@163.com?????????????????????? }
{*******************************************************}
unit DBDateTimePicker;
interface
uses
? SysUtils, Classes, Controls, ComCtrls, DBCtrls, Messages, DB;
type
? TDBDateTimePicker = class(TDateTimePicker)
? private
??? FDataLink: TFieldDataLink;
??? procedure CMGetDataLink(var Msg: TMessage);message CM_GETDATALINK;
??? procedure DataChange(Sender: TObject);
??? procedure EditingChange(Sender: TObject);
??? procedure FSetDataField(AValue: string);
??? procedure FSetDataSource(AValue: TDataSource);
??? procedure FSetReadOnly(AValue: Boolean);
??? procedure ShowData;
??? procedure UpdateData(Sender: TObject);
??? function FGetDataField: string;
??? function FGetDataSource: TDataSource;
??? function FGetField: TField;
??? function FGetReadOnly: Boolean;
? protected
??? procedure Change;override;
??? procedure Notification(AComponent: TComponent;Operation: TOperation);override;
? public
??? constructor Create(AOwner: TComponent); override;
??? destructor Destroy; override;
??? property Field: TField read FGetField;
? published
??? property DataField: string read FGetDataField write FSetDataField;
??? property DataSource: TDataSource read FGetDataSource write FSetDataSource;
??? property ReadOnly: Boolean read? FGetReadOnly write FSetReadOnly;
? end;
procedure Register;
implementation
uses? Variants;
constructor TDBDateTimePicker.Create(AOwner: TComponent);
begin
? inherited Create(AOwner);
? FDataLink := TFieldDataLink.Create;
? FDataLink.OnDataChange := DataChange;
? FDataLink.Control := self;
? FDataLink.OnEditingChange := EditingChange;
? FDataLink.OnUpdateData := UpdateData;
? self.DateTime := Now();
end;
?
destructor TDBDateTimePicker.Destroy;
begin
? FDataLink.Free;
? inherited;
end;
?
procedure TDBDateTimePicker.CMGetDataLink(var Msg: TMessage);
begin
? Msg.Result := Integer(FDataLink);
end;
?
procedure TDBDateTimePicker.DataChange(Sender: TObject);
begin
? if Field<>nil then
??? if Field.Value = null then
????? if (DataSource.DataSet.State = dsEdit)
??????? or (DataSource.DataSet.State = dsInsert) then
??????? Field.AsDateTime := Now();
? ShowData;
end;
?
procedure TDBDateTimePicker.EditingChange(Sender: TObject);
begin
? if (DataSource <> nil) and (DataField <> '') then
??? FDataLink.Edit;
end;
?
procedure TDBDateTimePicker.FSetDataField(AValue: string);
begin
? FDataLink.FieldName := AValue;
end;
?
procedure TDBDateTimePicker.FSetReadOnly(AValue: Boolean);
begin
? FDataLink.ReadOnly := AValue;
end;
?
procedure TDBDateTimePicker.ShowData;
begin
? if (DataSource <> nil) and (DataField <> '') and(Field<>nil)then
? begin
??? case Kind of
??? dtkDate: if Field.AsString <> '' then
?????????????? self.Date := Field.AsDateTime
???????????? else
?????????????? self.Date := Now();
??? dtkTime: if Field.AsString <> '' then
??????? ???????self.Time := Field.AsDateTime
???????????? else
?????????????? self.Time := Now();?
??? else
????? self.DateTime := Now();
??? end;
? end;
end;
?
procedure TDBDateTimePicker.FSetDataSource(AValue: TDataSource);
begin
? FDataLink.DataSource := AValue;
? if AValue <> nil then
??? AValue.FreeNotification(self);
end;
?
procedure TDBDateTimePicker.Change;
begin
? if (DataSource <> nil) and (DataField <> '') then
? begin
??? FDataLink.Edit;
??? Field.Value := self.Text;
? end;
? inherited Change;
end;
?
procedure TDBDateTimePicker.Notification(AComponent: TComponent;Operation: TOperation);
begin
? if (Operation = opRemove) and (FDataLink <> nil) and
????? (AComponent = DataSource) then
??? DataSource := nil;
end;
?
procedure TDBDateTimePicker.UpdateData(Sender: TObject);
var
? t: TFieldType;
begin
? if (DataSource <> nil) and (DataField <> '') then
? begin
??? t := FDataLink.Field.DataType;
??? case t of
??? ftTime: FDataLink.Field.AsDateTime := self.Time;
??? ftDate: FDataLink.Field.AsDateTime := self.Date;
??? ftDateTime: FDataLink.Field.AsDateTime := self.DateTime;
??? end;
? end;
end;
?
function TDBDateTimePicker.FGetDataField: string;
begin
? result := FDataLink.FieldName;
end;
?
function TDBDateTimePicker.FGetDataSource: TDataSource;
begin
? result := FDataLink.DataSource;
end;
?
function TDBDateTimePicker.FGetField: TField;
begin
? result := FDataLink.Field;
end;
?
function TDBDateTimePicker.FGetReadOnly: Boolean;
begin
? result := FDataLink.ReadOnly;
end;
procedure Register;
begin
? RegisterComponents('Linco', [TDBDateTimePicker]);
end;
end.
?
談到開發數據敏感控件就不得不說數據連接(DataLink),數據連接有很多種,開發數據敏感控件最常用到的就是字段數據連接(TFieldDataLink)。數據連接是聯系數據敏感控件和數據庫的通道。在數據敏感控件中就是憑借著數據連接來處理數據的更新和顯示的。從后邊我們的描述中您將更加能體會到,正是數據連接把數據在數據庫中的表示反映到用戶界面中,也是數據連接把數據從用戶界面更新到數據庫中。數據連接就是一個“大媒人”(這其實是設計模式中Mediator中介者模式的典型應用)。
既然字段數據連接這么重要,我們就先來系統的介紹一下它吧!TfieldDataLink閃亮登場!!!
TfieldDataLink的屬性:
(1)、property CanModify: Boolean;表示這個字段是不是只讀的。
(2)、property Control: TComponent;指定這個字段數據連接被連接到哪個數據敏感控件。因為字段數據連接要把它的狀態改變通知包含它的數據敏感控件。
(3)、property Editing: Boolean; 表示這個字段是不是可以被編輯。
(4)、property Field: TField;表示這個字段數據連接連接的字段。
(5)、property Active: Boolean;表示字段數據連接連接的數據集是否處于激活狀態。
(6)、property FieldName: String;字段名。
(7)、property DataSource: TDataSource;表示它連接的數據源。
(8)、property DataSet: TDataSet;表示它負責維護的數據集。
方法:
(1)、function Edit: Boolean;嘗試設置字段為編輯狀態。如果設置成功則返回True,反之返回False;
事件:
(1)、property OnActiveChange: TNotifyEvent;當Active屬性變化的時候發生此事件。
(2)、property OnDataChange: TNotifyEvent;當數據集發生變化的時候發生。
(3)、property OnEditingChange: TNotifyEvent;當數據源從編輯狀態變為其他狀態或從其他狀態變為編輯狀態的時候發生。
(4)、property OnUpdateData: TNotifyEvent;當向數據庫提交對數據庫的修改時發生此事件。
代碼分析:
(1)、做為一個數據敏感控件,它首先要實現的功能就是允許用戶將此控件連接到一個數據源(DataSource)。我們還要用戶能選擇這個控件綁定到哪個字段。
將控件連接到一個數據源,而數據源又是一個控件,所以這就是一個關聯控件屬性方法的應用。FsetDataSource中FDataLink.DataSource := AValue;這句代碼是最重要的。就像我們前面講到的數據連接就是一個在數據源和數據敏感控件之間的媒人,所以數據源(DataSource)要告訴媒人是它要被連接到數據敏感控件,而不是別人,告訴媒人的唯一方法就是設定媒人的DataSource為自己(即要綁定的數據源)。因為我們的顯示日期的控件只能顯示一個字段,還要告訴媒人自己的哪個字段要綁定到數據敏感控件,這個通過數據敏感控件的FieldName屬性來進行。即:
procedure TDBDateTimePicker.FSetDataField(AValue: string);
begin
? FDataLink.FieldName := AValue;
end;
(2)、我們還可以為控件增加一個Field屬性,這樣用戶就可以通過DBDateTimePicker.Field.AsString = ‘ok’;這樣的方式對字段進行操作了。當然了,這最終還是通過數據連接的Field屬性來進行的。
(3)、由于VCL內部通信機制的要求,數據敏感控件要響應CM_GETDATALINK事件。只要在事件相應函數里邊把消息的Result域賦值為DataLink的地址就可以了。也就是:
procedure TDBDateTimePicker.CMGetDataLink(var Msg: TMessage);
begin
? Msg.Result := Integer(FDataLink);
end;
(4)、就像DBEdit一樣,在用戶通過改變控件中的日期時,應該能將改動保存到數據庫字段中。我們覆蓋控件的調度方法Change(在顯示的數據變化時被調用)以將變化保存到數據庫中。
procedure TDBDateTimePicker.Change;
begin
? if (DataSource <> nil) and (DataField <> '') then
? begin
??? FDataLink.Edit;//設置數據連接為編輯狀態,由這個媒人將數據庫綁定的字//段設置為編輯狀態
??? Field.Value := self.Text;//設定數據字段的值
? end;
? inherited Change;
end;
(5)、回頭再來看看構造函數吧!
?? ?FDataLink.OnDataChange := DataChange;
?? FDataLink.OnEditingChange := EditingChange;
? FDataLink.OnUpdateData := UpdateData;
? FDataLink.Control := self;
前三句是設定響應數據連接事件處理句柄,正是這三句把數據庫中的數據與用戶界面聯系了起來。關于這三個事件處理句柄的實現請參加源代碼,這里就不多說了。
思考題:
1、做一個顯示是/否的數據敏感控件,當這個控件與一個布爾類型的字段連接的時候,如果字段的值是0則顯示“否”,如果字段的值是1則顯示“是”;同時可以接受用戶的修改,當用戶在控件上單擊一次鼠標,布爾值就翻轉一次。