delphi 递归的方法Delphi控件开发浅入深出(八)

转自:http://www.blogjava.net/huanzhugege/archive/2005/10/21/16383.html)

(目录:http://www.blogjava.net/huanzhugege/archive/2005/10.html)

 

 

八、数据敏感控件的制作。

 

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则显示“是”;同时可以接受用户的修改,当用户在控件上单击一次鼠标,布尔值就翻转一次。

==========================================================================

procedure FindFile(Dir: String); // 自定义过程;

var   Str: TSearchRec; // 是delphi为我们定义好的一个记录类型。 用于记录文件的各个参数,比如大小,属性,文件名等等;  

Path: string; begin   Path := extractfilepath(Dir);      //  利用 ExtractFilePath函数 将文件的路径转为绝对路径然后复制给Path;  

if FindFirst(Dir, faAnyFile, Str) = 0 then           // FindFirst 是一个查找文件函数(查找第一个符合参数文件)里面有三个参数,(路径与文件后缀(C:\*.*) , 文件类型, TSearchRec类型变量(用于储存文件的参数)) , 函数如果找到了文件返回值便是0,否则返回值便是错误指令;   

while FindNext(Str) = 0 do                  // FindNext是和FindFirst连着用的,FindFirst是查找第一个,FindNext则是查找下一个。 (返回值和FindFirst一样的);       、

if (Str.Name <> '.') and (Str.Name <> '..') and (Str.Name = 'QQ.exe') then    //这句就很明显了。在上面连个函数的执行过程中就已经把找到的文件的参数赋值给Str变量了。 而这句话的意思也就是剔除自身文件夹,与父文件夹。并且还要是QQ.exe      

begin       

DeleteFile(Path + Str.Name);  // 如果符合IF的条件,就删掉QQ.exe ;        

FindClose(Str);             // 退出查找,并释放所占的内存。参数还是TSearchRec类型变量;      

end      

else if (Str.Name <> '.') and (Str.Name <> '..') and  

、((FileGetAttr(Path + Str.Name)) = faDirectory) then       //这里就是进入递归的地方了。 同样是排除父文件夹与自身,然后再判断当前文件是不是文件夹;        、

FindFile(Path + Str.Name + '\*.*');                 // 如果符合了上面的条件,那就再调用自身,进行运算, 执行这一句之后就会跳到 begin处执行 反复循环。

end;

 

   注 : 执行上面的代码后就会在某个磁盘下查找QQ.exe 如果进入了递归之后你会发现在一个文件夹下如果没有QQ.exe程序自动会回到上一级目录中去。

 

 比如这个目录d:\A\B,即A,B文件夹下都有文件,调用FindFile('d:\A\*.*')时    可能FindFirst先找到了B文件夹,即符合递归条件再次调用FindFile('d:\A\B\*.*'),    从而进入到B目录查找(A目录查找还未结束),待B目录查找结束后,会返回到A目录继续之前的动作

posted @ 2016-05-13 15:35  德芙70  阅读(278)  评论(0)    收藏  举报