八、数据敏感控件的制作。
Delphi的一大亮点就是它的数据库开发能力。而数据敏感组件则在这中间起着很重要的作用。在DelphiData Control页面下的控件都是用于显示和编辑数据库中的数据的。相信大家已经体会到数据敏感控件的好处了。我们这一节就给大家演示一下数据敏感控件的开发方法。
需要提醒大家的是,不像其他体系的控件,数据敏感控件并没有一个统一的基类,只要是从TwinControl类或其子类派生就可以,数据敏感控件的特殊之处就在于我们下面提到的数据连接。
相信用Delphi开发过数据库的人一定对delphi中没有一个日期数据敏感控件而恼火。每次都要我们自己处理数据的更新与显示。所以我们就来开发一个DBDateTimePicker控件。
新建一个控件,从TdateTimePicker派生,源代码如下:
{*******************************************************}
{       Linco TDBDateTimePicker
{       mail me: [email]about521@163.com[/email]                       }
{*******************************************************}
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)。我们还要用户能选择这个控件绑定到哪个字段。
将控件连接到一个数据源,而数据源又是一个控件,所以这就是一个关联控件属性方法的应用。FsetDataSourceFDataLink.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则显示“是”;同时可以接受用户的修改,当用户在控件上单击一次鼠标,布尔值就翻转一次。