设为首页 加入收藏

TOP

CBuilder高手进阶(三)用BCB设计DBTreeView组件
2012-11-01 11:23:55 】 浏览:1120
Tags:CBuilder 高手进阶 BCB 设计 DBTreeView 组件


(三)用BCB设计DBTreeView组件  

 

一、 系统分析  

现在使用BCB的以越来越多,可是你有没有意识到,你所使用的控件究竟有几个是用C 编写的?答案肯定让人无法忍受,既然无法忍受何不亲自操刀写一套属于BCB自己的组件呢?我希望从我开始,众位高手能同心协力,共同打造真正属于BCB自己的组件!我不想讲述BCB设计组件的基础知识,因为这些在《C Builder 5开发人员指南》中有详细的介绍。  

我们所设计的DBTreeView派生自TtreeView组件,因此它将具有TtreeView组件的一切特性,我们要做的就是使其具有数据感知能力,与数据直接连接进行直接通信。要构造Tree形结构,我们应使每一个节点具备如下特征:每个节点有一个唯一标识符ID和一个父标识ParentID,(最顶层节点的Parent为空)为了显示相关内容,则还需要一上显示字段FDisplayField。该组件的工作原理已经很清晰了:通过每个节点的ID与ParentID定位数据记录,然后将相关字段内容赋给FdisplayField显示。  

二、 开发前沿  

该组件中最重的技术莫过于数据感知了,下面就介绍一下数据感知 技术:  

要使某个组件成为数据感知的,我们必须给它提供所需的数据连接以便和数据训数据成员通信,这个数据连接类是TFieldDataLink。数据感知组件有其自己的数据连接类,数据连接由组件负责创建、初始化和销毁。建立连接通常需要3步:  

1. 将数据连接类(TFieldDataLink)声明为组件的成员;  

2. 声明适当的读、写访问属性;  

3. 初始化数据连接。  

三、 设计流程  

//DBTreeView头文件  

/在头文件中添加以下三行,因为/数据连接类需要这三个头文件中的声明  

#include <Db.hpp>  

#include <DbTables.hpp>  

#include <DbCtrls.hpp>  

//定义事件,用来响应设置节点位图事件  

typedef void __fastcall (__closure *TDBTVSetImageIndexEvent)(System::TObject* Sender, int ID, int ParentID, int Level, int& ImageIndex, int& SelectedIndex);  

//定义节点结构  

struct TDBTreeNodeData  

{  

int ID; //节点唯一标识  

int ParentID; //节点父标识,它为空时表示此节点是最顶层节点  

};  

 

class PACKAGE TDBTreeView : public TTreeView  

{  

private:  

TFieldDataLink* FDataLink; //声明数据连接成员  

AnsiString FParentIDField; //父标识字段  

AnsiString FPrimaryIDField; //主标识字段  

AnsiString FDisplayField; //显示字段  

bool FActive; //是否为活动状态  

bool FAllowModifyDB; //是否允许修改  

TDBTVSetImageIndexEvent FOnSetImageIndex; //声明事件  

 

void __fastcall SetActive(bool Value); //设置状态  

void __fastcall SetDataSource(Db::TDataSource* Value); //设置数据源  

TDataSource* __fastcall GetDataSource(); //取得数据源  

 

void __fastcall ClearAllNodes(); //删除所有节点  

void __fastcall FillTreeNodes(int ParentID, TTreeNode* Node); //填充节点  

void __fastcall FillChildTreeNodes(int ParentID, TTreeNode* Node, bool Nest = false); //填充子节点  

void __fastcall AddDataToNode(TTreeNode* Node, TDBTreeNodeData& Data);//追加数据到节点  

protected:  

//以下以个方法用来重载TreeView的相关方法  

virtual void __fastcall Loaded(void); //Load方法  

virtual void __fastcall Notification(Classes::TComponent* AComponent, Classes::TOperation Operation); //事件  

DYNAMIC void __fastcall Edit(const tagTVITEMA &Item);//Edit方法  

DYNAMIC void __fastcall Expand(TTreeNode* Node);//Expand方法  

DYNAMIC void __fastcall KeyDown(Word &Key, Classes::TShiftState Shift);//键盘事件  

DYNAMIC void __fastcall Change(TTreeNode* Node);//节点改变事件  

virtual bool __fastcall CustomDrawItem(TTreeNode* Node, TCustomDrawState State, TCustomDrawStage Stage, bool &PaintImages);//自绘节点事件  

 

public:  

__fastcall TDBTreeView(TComponent* Owner);//构造函数  

__fastcall ~TDBTreeView();//析构函数  

 

TDBTreeNodeData __fastcall GetNodeData(TTreeNode* Node);//取得指定节点数据  

void __fastcall FullExpand(void); //展开所有节点  

__property bool Active = {read = FActive, write = SetActive};//属性活动状态  

__published:  

//以下定义在属性编辑器中用到的几个属性  

__property bool AllowModifyDB = {read = FAllowModifyDB, write = FAllowModifyDB};//是否允许编辑  

__property AnsiString ParentIDField = {read = FParentIDField, write = FParentIDField};//父标识  

__property AnsiString PrimaryIDField = {read = FPrimaryIDField, write = FPrimaryIDField};//主标识  

__property AnsiString DisplayField = {read = FDisplayField, write = FDisplayField};//显示字段  

__property TDataSource* DataSource = {read=GetDataSource, write=SetDataSource};//数据源  

__property TDBTVSetImageIndexEvent OnSetImageIndex = {read=FOnSetImageIndex, write=FOnSetImageIndex};//设置节点位图事件  

};  

//下面的类用来填充字段列表  

class TFieldNameProperty : public TStringProperty  

{  

public:  

TPropertyAttributes __fastcall GetAttributes(void)  

{  

return TPropertyAttributes() << paValueList;  

}  

 

void __fastcall GetValues(Classes::TGetStrProc Proc);  

};  


(三)用BCB设计DBTreeView组件(续一)  


//DBTreeView详细设计.CPP文件  

__fastcall TDBTreeView::TDBTreeView(TComponent* Owner)  

: TTreeView(Owner)  

{  

//在构造函数中对相关数据进行初始化  

FDataLink = new TFieldDataLink; //建立数据连接类  

FParentIDField = ""; //将节点父标识设置为空  

FPrimaryIDField = ""; //将节点主标识设置为空  

FDisplayField = ""; //将显示字段设置为空  

FActive = false; //设置为非活动状态  

FAllowModifyDB = false; //设置数据集不允许修改  

}  

__fastcall TDBTreeView::~TDBTreeView()  

{//在析构函数中释放构造函数分配的资源  

delete FDataLink; //释放数据连接类  

}  

void __fastcall TDBTreeView::SetActive(bool Value)  

{  

//设置状态是否为活动  

if (FActive != Value) //如果指定状态与当前状态一样,则不做处理,直接返回  

{//否则  

if (Value)//如果指定值为true则  

{  

if (FDataLink->DataSource == NULL)//如果未指定数据源则抛出一个异常  

throw Exception("Missing DataSource property.");  

if (FDataLink->DataSource->DataSet == NULL)//否则,如果数据源中未指定数据集,也抛出一个异常  

throw Exception("Invalid DataSource.");  

 

if (FParentIDField == "")//如果父标识字段为空,抛出一个异常  

throw Exception("Missing ParentIDField property.");  

if (FPrimaryIDField == "")//如果主标识字段为空,抛出一个异常  

throw Exception("Missing PrimaryIDField property.");  

if (FDisplayField == "")//如果显示字段为空抛出一个异常  

throw Exception("Missing DisplayField property.");  

//补充说明:从以上几个异常您应该不难看出我们这个组件需要的环境:  

//必须指定数据源,数据集,节点父标识字段,主标识字段,显示字段  

ClearAllNodes();//删除原来所有节点  

FillTreeNodes(0, NULL);//填充所有数据到各节点  

}  

else  

{//如果指定值为false则  

ClearAllNodes(); //删除所有节点  

}  

FActive = Value; //将指定值赋给FActive  

}  

}  

void __fastcall TDBTreeView::SetDataSource(Db::TDataSource* Value)  

{//设置数据源  

if (Value != FDataLink->DataSource)//如果指定值与当前值不一样,则  

{  

FDataLink->DataSource = Value; //将数据源当前值设置为指定值  

}  

}  

TDataSource* __fastcall TDBTreeView::GetDataSource()  

{//获取数据源  

return FDataLink->DataSource; //返回与数据源  

}  

void __fastcall TDBTreeView::ClearAllNodes()  

{//删除所有节点,调用TreeView的相关方法进行遍历式节点删除  

for (int i=0; i<this->Items->Count; i )  

delete (TDBTreeNodeData*)(Items->Item[i]->Data);  

 

this->Items->BeginUpdate();  

this->Items->Clear();  

this->Items->EndUpdate();  

}  

void __fastcall TDBTreeView::FillTreeNodes(int ParentID, TTreeNode* Node)  

{//填充数据到节点  

TQuery* AQuery = new TQuery(this);//创建一个数据集控件  

AnsiString strSql, strText;  

int iID, iParentID;  

int iImageIndex, iSelectedIndex;  

TTreeNode* TreeNode;  

TDBTreeNodeData NodeData;  

//用指定条件打开相关数据表  

AQuery->DatabaseName = ((TTable*)(FDataLink->DataSource->DataSet))->DatabaseName;  

AQuery->Close();//关闭数据集  

AQuery->SQL->Clear();//清空原查询条件  

 

strSql = "SELECT * FROM " ((TTable*)(FDataLink->DataSource->DataSet))->TableName " WHERE ";  

strSql = FParentIDField "=:PID";  

 

AQuery->SQL->Add(strSql);//指定新的查询条件  

AQuery->ParamByName("PID")->AsInteger = ParentID;//取得父标识字段内容  

AQuery->Open();//打开数据集  

 

this->Items->BeginUpdate();//准备更新DBTreeView显示  

 

while (!AQuery->Eof)//数据没有到数据集尾,则  

{  

strText = AQuery->FieldByName(FDisplayField)->AsString;//取得显示字段内容  

iID = AQuery->FieldByName(FPrimaryIDField)->AsInteger;//取得主标识字段内容  

iParentID = AQuery->FieldByName(FParentIDField)->AsInteger;//取得父标识字段内容  

 

TreeNode = this->Items->AddChild(Node, strText);//添加此子节点  

iImageIndex = iSelectedIndex = -1;//将节点位图与选中时位图设置为空(-1)  

if (FOnSetImageIndex) FOnSetImageIndex(this, iID, iParentID, TreeNode->Level, iImageIndex, iSelectedIndex);//如果位图设置发生变化,则触发相关事件  

TreeNode->ImageIndex = iImageIndex;//设置节点位图及选中时的位图  

TreeNode->SelectedIndex = iSelectedIndex;  

 

NodeData.ID = iID;//设置节点标识及父标识  

NodeData.ParentID = iParentID;  

AddDataToNode(TreeNode, NodeData);//追加数据到指定节点  

 

FillChildTreeNodes(iID, TreeNode, false);//填充子节点  

AQuery->Next(); //移到下条记录  

}  

 

this->Items->EndUpdate(); //更新DBTreeView组件  

 

AQuery->Close(); //关闭数据集  

delete AQuery; //删除临时创建的数据集控件  

}  

(三)用BCB设计DBTreeView组件(续二)  

void __fastcall TDBTreeView::FillChildTreeNodes(int ParentID, TTreeNode* Node, bool Nest)  

{//用指定值填充子节点  

TQuery* AQuery = new TQuery(this);//创建一个数据集控件  

AnsiString strSql, strText;  

int iID, iParentID;  

int iImageIndex, iSelectedIndex;  

TTreeNode* TreeNode;  

TDBTreeNodeData NodeData;  

//设置数据集的各项参数  

AQuery->DatabaseName = ((TTable*)(FDataLink->DataSource->DataSet))->DatabaseName;//设置数据库名  

AQuery->Close();//关闭数据集  

AQuery->SQL->Clear();//清空原SQL语句  

 

strSql = "SELECT * FROM " ((TTable*)(FDataLink->DataSource->DataSet))->TableName " WHERE ";  

strSql = FParentIDField "=:PID";  

 

AQuery->SQL->Add(strSql);//指定新的SQL语句  

AQuery->ParamByName("PID")->AsInteger = ParentID;  

AQuery->Open();//打开数据集  

 

this->Items->BeginUpdate();//开始更新DBTreeView组件显示  

 

while (!AQuery->Eof)//未到数据集尾部  

{  

strText = AQuery->FieldByName(FDisplayField)->AsString;//取得显示字段内容  

iID = AQuery->FieldByName(FPrimaryIDField)->AsInteger;//取得主标识字段内容  

iParentID = AQuery->FieldByName(FParentIDField)->AsInteger;//取得父标识字段内容  

 

TreeNode = this->Items->AddChild(Node, strText);//将上面取得的相关数据追加到新节点  

iImageIndex = iSelectedIndex = -1;//设置节点位图与选中时位图  

if (FOnSetImageIndex) FOnSetImageIndex(this, iID, iParentID, TreeNode->Level, iImageIndex, iSelectedIndex);//如果设置位图发生变化,则触发相关事件  

TreeNode->ImageIndex = iImageIndex;//设置节点位图及选中时位图  

TreeNode->SelectedIndex = iSelectedIndex;  

 

NodeData.ID = iID;//取得节点主标识,父标识,并按其追加一个新节点  

NodeData.ParentID = iParentID;  

AddDataToNode(TreeNode, NodeData);  

 

if (Nest) FillChildTreeNodes(iID, TreeNode);//如果指定参数Nest为true,则递归调用以填充所有子节点  

AQuery->Next();//移动到下一条记录  

}  

 

this->Items->EndUpdate();//结束DBTreeView组件更新  

 

AQuery->Close();//关闭数据集  

delete AQuery;//删除临时创建的数据集控件  

}  

void __fastcall TDBTreeView::AddDataToNode(TTreeNode* Node, TDBTreeNodeData& Data)  

{//追加数据到节点  

TDBTreeNodeData* pData = new TDBTreeNodeData;  

*pData = Data;  

Node->Data = pData;  

}  

//---------------------------------------------------------------------------  

TDBTreeNodeData __fastcall TDBTreeView::GetNodeData(TTreeNode* Node)  

{//取得指定节点的数据  

return *(TDBTreeNodeData*)(Node->Data);  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::Loaded(void)  

{//调用原Load方法  

TCustomTreeView::Loaded();  

/* TODO : Loaded */  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::Notification(Classes::TComponent* AComponent, Classes::TOperation Operation)  

{  

//事件响应  

TCustomTreeView::Notification(AComponent, Operation);//调用原方法  

if ((Operation == opRemove) && (FDataLink != NULL) && (AComponent == DataSource))//如果操作为opRemove以及数据连接不为空且指定组件为数据源,则  

DataSource = NULL;//使数据源为空  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::Edit(const tagTVITEMA &Item)  

{//编辑节点  

TCustomTreeView::Edit(Item);//调用原方法  

 

if (FAllowModifyDB)//如果允许修改,则  

{  

TTreeNode* Node;  

AnsiString DatabaseName = ((TTable*)(FDataLink->DataSource->DataSet))->DatabaseName;  

AnsiString TableName = ((TTable*)(FDataLink->DataSource->DataSet))->TableName;  

TDBTreeNodeData NodeData;  

TQuery* AQuery = new TQuery(this);//新建一个数据集  

AnsiString strSql;  

 

if ((Item.state & TVIF_PARAM) != 0) Node = (TTreeNode*)(Item.lParam);  

else Node = Items->GetNode(Item.hItem);  

 

NodeData = GetNodeData(Node);  

strSql = "UPDATE " TableName " SET " FDisplayField " =:NewDispText WHERE " FPrimaryIDField "=:ID";//用指定条件更新数据集  

 

AQuery->Close();//关闭数据集  

AQuery->DatabaseName = DatabaseName;//设置数据库连接  

AQuery->SQL->Clear();//清空原SQL语句  

AQuery->SQL->Add(strSql);//追加SQL语句  

AQuery->ParamByName("NewDispText")->AsString = Node->Text;//对SQL中的参数进行赋值  

AQuery->ParamByName("ID")->AsInteger = NodeData.ID;  

AQuery->ExecSQL();//打开数据集  

 

delete AQuery;//删除临时创建的数据集控件  

}  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::Expand(TTreeNode* Node)  

{//展开指定节点  

TCustomTreeView::Expand(Node);//调用原原方法  

 

TDBTreeNodeData NodeData;  

TTreeNode* ANode;  

 

for (ANode = Node->getFirstChild(); ANode; ANode = Node->GetNextChild(ANode))//遍历指定节点的子节点  

{  

NodeData = GetNodeData(ANode);//取得节点数据  

if (ANode->Count == 0)//如果节点数据为0,则填充其子节点  

FillChildTreeNodes(NodeData.ID, ANode);  

}  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::KeyDown(Word &Key, Classes::TShiftState Shift)  

{  

//键盘处理  

TWinControl::KeyDown(Key, Shift);//调用原键盘处理方法  

 

if (Key == VK_F2 && Shift == TShiftState())//如果是Shift F2则  

{  

/* Handle ’F2’ key */  

if (this->Selected != NULL)//编辑当前选中的节点  

this->Selected->EditText();  

}  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::Change(TTreeNode* Node)  

{//处理节点发生变化事件  

TCustomTreeView::Change(Node);//调用原方法  

static TTreeNode* OldNode = NULL;  

TTreeNode* SelectedNode = this->Selected;//取得已选节点指针  

TDBTreeNodeData NodeData;  

TTable *ATable = (TTable*)(FDataLink->DataSource->DataSet);//指定数据集  

 

if (OldNode == SelectedNode) return;//如果旧节点等于选中的节点则直接返回  

if (ATable == NULL) return;//如果数据集为空则直接返回  

if (SelectedNode == NULL) return;//如果没有已选节点则直接返回  

NodeData = GetNodeData(SelectedNode);//取得指定节点数据  

ATable->SetKey();//按指定条件查找数据记录  

ATable->FieldByName(FPrimaryIDField)->AsInteger = NodeData.ID;  

ATable->GotoKey();  

OldNode = SelectedNode;//旧节点等于现在已选节点  

}  

//---------------------------------------------------------------------------  

bool __fastcall TDBTreeView::CustomDrawItem(TTreeNode* Node, TCustomDrawState State, TCustomDrawStage Stage, bool &PaintImages)  

{  

//自绘节点  

bool Result;//调用原自绘方法  

Result = TCustomTreeView::CustomDrawItem(Node, State, Stage, PaintImages);  

 

/* TODO : ... */  

//TDBTreeNodeData NodeData = GetNodeData(Node);  

return Result;  

}  

//---------------------------------------------------------------------------  

void __fastcall TDBTreeView::FullExpand(void)  

{//展开所有节点  

if (!Active)//如果DBTreeView不是活动状态,则抛出一个异常  

throw Exception("DBTreeView is Inactive.");  

this->Items->BeginUpdate();//开始更新DBTreeView  

ClearAllNodes();//清空所有节点  

FillChildTreeNodes(0, NULL, true);//填充子节点  

this->Items->EndUpdate();//结束更新  

TCustomTreeView::FullExpand();//调用原方法实现展开所有节点  

}  

 

//---------------------------------------------------------------------------  

void __fastcall TFieldNameProperty::GetValues(Classes::TGetStrProc Proc)  

{//取得字段值  

int i;  

TDBTreeView* ADBTreeView;  

 

ADBTreeView = (TDBTreeView*)GetComponent(0);  

if (ADBTreeView->DataSource != NULL)//如果DBTreeView的数据源不为空,则  

{//遍历所有字段,并将其填充到相关字段属性中  

for (i = 0; i < ADBTreeView->DataSource->DataSet->FieldCount; i )  

Proc(ADBTreeView->DataSource->DataSet->Fields->Fields[i]->FieldName);  

}  

else  

{//否则抛出异常  

throw Exception("Missing DataSource property.");  

}  

}  

TTypeInfo* AnsiStringTypeInfo(void)  

{  

//定义类型信息  

TTypeInfo* TypeInfo = new TTypeInfo;  

TypeInfo->Name = "AnsiString";  

TypeInfo->Kind = tkLString;  

return TypeInfo;  

}  


(三)用BCB设计DBTreeView组件(小结)  
版权声明:CSDN是本Blog托管服务提供商。如本文牵涉版权问题,CSDN不承担相关责任,请版权拥有者直接与文章作者联系解决。  

 

用BCB设计DBTreeView组件小结  

续二的最后一个函数,你是不是感到很纳闷:这个函数到底是用来干什么的呢?下面听我慢慢道来:我使用这个函数主要是用来实现定制属性编辑器。细心的读者一定还记得,我设计的控件中有三个关键的数据成员:ParentID,PrimaryIDField,DisplayField,这三个成员的函义是什么?相信不用我多说。为了使它们的设置更人性化,我选择了用下拉框的方式显示它,而其具体内容则由数据表中的字段而来,那么很自然我便采用了TFieldNameProperty,通过对它的定制,我实现了属性编辑器的定制。  

TTypeInfo* AnsiStringTypeInfo(void)  

{  

//定义类型信息  

TTypeInfo* TypeInfo = new TTypeInfo;  

TypeInfo->Name = "AnsiString";  

TypeInfo->Kind = tkLString;  

return TypeInfo;  

}  

namespace Dbtreeview  

{  

//注册组件  

void __fastcall PACKAGE Register()  

{  

RegisterPropertyEditor(AnsiStringTypeInfo(), __classid(TDBTreeView), "ParentIDField", __classid(TFieldNameProperty));//注册属性  

RegisterPropertyEditor(AnsiStringTypeInfo(), __classid(TDBTreeView), "PrimaryIDField", __classid(TFieldNameProperty));//注册属性  

RegisterPropertyEditor(AnsiStringTypeInfo(), __classid(TDBTreeView), "DisplayField", __classid(TFieldNameProperty));//注册属性  

TComponentClass classes[1] = {__classid(TDBTreeView)};  

RegisterComponents("Data Controls", classes, 0);//注册组件  

}  

}  

到此为止,我已给出了用BCB实现DBTreeView的所有思路及代码,不知您感觉如何?要是觉得好就给点鼓励;要是觉得差,您也可以直言(好像是江湖卖艺^),我希望大家共同进步!  

通过对这个组件的编写实践,你应该会用BCB进行一些组件的开发了吧?这里我再总结一下该组件设计中的要点,并为您以后设计组件提供一点建议:  

1. 编写一个新的组件,首先要考虑从哪派生。你需要熟悉一下VCL结构,大致各种可视组件、不可视组件、窗口组件等的继承关系。建议你看看《C Builder 5开发人员指南》和《C Builder 深度历险》。  

2. 事件定义。事件其实就是某件事情发生时要做的处理,以DBTreeView中定制带参数的方法为例,我阐述一下定义的件的方法:  

typedef void __fastcall (__closure *TDBTVSetImageIndexEvent)(System::TObject* Sender, int ID, int ParentID, int Level, int& ImageIndex, int& SelectedIndex);  

TDBTVSetImageIndexEvent是自定义的事件类型,ID,ParentID等是事件所需的参数。那么就可以这样定义事件:  

Private:  

TDBTVSetImageIndexEvent FonSetImageIndex;  

__published:  

__property TDBTVSetImageIndexEvent OnSetImageIndex = {read=FOnSetImageIndex, write=FOnSetImageIndex};  

3. 定义消息。虽然我的DBTreeView中没使用消息,但消息的作用绝不容忽视。在BCB中定义消息的方法如下:  

private:  

void __fastcall OnMyMessage(Tmessage &Msg);//定义消息函数  

BEGIN_MESSAGE_MAP  

MESSAGE_HANDLE(消息,消息类型,消息函数)  

END_MESSAGE_MAP(父类)  
 

 


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇CBuilder高手进阶(四)动态显示.. 下一篇如何用BCB获取应用图标

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目