ilikenba 发表于 2004-12-22 15:18

[转帖]C++ Builder 高手进阶

<DIV class=HtmlCode>C++ Builder 高手进阶
(一) 编写弹出广告杀手

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>

一、系统分析

作为一名软件开发人员,必然会经常上网查找资料,可讨厌的广告真让人心烦。有没有办法自动关闭这些广告呢?答案是肯定的!也许你会说:“网上这类软件多的是,随便找一个不就行了?”,你说的确实不错,可作为一名软件开发人员,总不能什么都靠别人吧?自己动手作一个如何?说干就干,首先得找出弹出广告窗口的工作原理:

1. 使用自己的《系统窗口分析器》(在下一期讲述其开发过程),轻易地就发现弹出广告窗口的特征:其窗口类是CabinetWClass或IEFrame。

2. 如果找出窗口类是CabinetWClass或IEFrame的窗口,向其发送WM_CLOSE不就OK了吗?心里不由一阵窃喜!

3. 使用定时器定时进行检测,找到符合条件的窗口就向其发送WM_CLOSE消息。

二、实战前沿

道理讲明了,可如何找到窗口类名呢?这就需要使用API函数了:

GetWindowText :取得窗口文本

GetWindow :取得窗口句柄

GetClassName :取得类名

PostMessage :发送消息

SetWindowLong :设置方式

具体用法请查阅相关资料。

三、设计流程

1. 启动C++Builder 5,新建一个Application,将Form的Name设置为Form1,其BoldStyle设置为bsNone,在Form的Icon中设置好图标。

2. 定义数据变量:

TRAYICON_ID = 1;

ICONEVENT = WM_APP + 100;

TNotifyIconData TrayIcon ;

3. 拖放一个Timer控件和一个Popmenu控件到窗体上。Timer的属性:Name-&gt;Timer1,Interval-&gt;1000;Popmenu1上追加三个子项:继续-&gt;ContinueCmd,PauseCmd-&gt;暂停,ExitCmd-&gt;退出。

4. 使应用程序进入系统托盘:void TrayIconCmd(void);

TrayIcon.cbSize = sizeof(TNotifyIconData);

TrayIcon.hWnd = Handle;

TrayIcon.uID = ID_TRAYICON;

TrayIcon.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;

TrayIcon.uCallBackMessage = ICONEVENT;

TrayIcon.hIcon = Form1-&gt;Icon-&gt;Handle;

TrayIcon.szTip = “广告窗口杀手”;

Shell_NotifyIcon(NM_ADD, &amp;TrayIcon);

5. 使应用程序退出系统托盘:void TrayIconExit(void);

Shell_NotifyIcon(NIM_DELETE, &amp;TrayIcon );

6. 使应用程序启动后直接进入系统托盘:编写Form1的OnCreate事件

TrayIconCmd(); //在系统托盘中安装图标

Width = 0;

SetWindowLong(Application-&gt;Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);

7. 从系统托盘中卸载图标:编写Form1的OnClose事件

TrayIconExit();

8. 自定义消息TrayIconOnClick:

在头文件.h中定义:

private:

MESSAGE void __fastcall TrayIconOnClick(Tmessage &amp;message);

BEGIN_MESSAGE_MAP

MESSAGE_HANDLER(ICONEVENT, Tmessage, TrayIconOnClick);

END_MESSAGE_MAP(Tform)

9. 鼠标点击系统托盘图标(TrayIconOnClick),则弹出菜单:

Tpoint p;

if( message.lParam == WM_LBUTTONDOWN ||

message.lPram == WM_RBUTTONDOWN)

{

PopupMenu1-&gt;PopupComponent = Form1;

SetForegroundWindow(Handle);

GetCursorPos(p);

PopupMenu-&gt;Popup(p.x,p.y);

}

10. 枚举指定窗口句柄的所有子窗口:void MENumChildWindows(HWND hand);

char tempstr;

bool IsPopWindow = true;

HWND h = GetWindow(hand, GW_CHILD);

while(h)

{

GetClassName(h, tempstr, 256);

if( !strcmp(tempstr, “WorkerA”) || !strcmp( tempstr, “WorkerW”))

if( IsWindowVisible(h) ) IsPopWindow = false;

h = GetWindow(h, GW_HWNDNEXT);

}

if( IsPopWindw )PostMessage(hand, WM_CLOSE, 0, 0);

11. 双击Timer控件,添加其事件OnTimer:

char Text;

HWnd h := GetWindow(Handle, GW_HWNDFIRST);

while(h)

{

if (GetWindowText(h, Text, 255) &gt; 0)

if (GetClassName(h,Text , 255)&gt;0)

if ( !strcmp( Text, “CabinetWClass”) || !strcmp(Text, “IEFrame”))

EnumChildWindows(h);

h = GetWindow(h, GW_HWNDNEXT);

}

12.编写菜单响应事件:

ContinueCmd:继续

Timer1-&gt;Enabled = true;

PauseCmd-&gt;Checked = false;

ContinueCmd-&gt;Checked = true;

PauseCmd:暂停

Timer1-&gt;Enabled = false;

PauseCmd-&gt;Checked = true;

ContinueCmd-&gt;Checked = false;

ExitCmd::退出

Close();



(二)系统窗口分析器

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>

记得《超级解霸》作者梁肇新先生曾在电脑报上发表过系列文章《开发手记》,写的极有深度,令我获益匪浅。其在文中称,每次要写一个新功能或新的界面时,总喜欢用spy++对现有类似软件进行分析,可非常容易地得到其采用的什么类或什么消息机制,从而可轻而易举地模仿出相关的功能。我在具体的软件开发工作中,也经常这样做,有时确实可起到事半功倍的作用。但本篇不是教你如何使用spy++,而是要教你如何自己编写一个类似于spy++的软件。

一、 系统分析

要实现spy++的功能,首先要知道如何获取当前窗口句柄,以及当前窗口类。程序中主要使用定时器实现实时窗口句柄采集。

二、 开发前沿

这里用到的API函数有:

SystemParametersInfo:取得指定的系统参数

SetWindowPos:设置窗口位置

GetCursorPos: //获得光标的位置

WindowFromPoint: //取得光标位置的窗口句柄

GetClassName://获得类名

SendMessage: //获得窗口信息

三、 设计流程

1.初始化应用程序窗口,使其总显示在最顶层:

void __fastcall TSpyForm::FormCreate(TObject *Sender)

{

//初始化

TRect R;

//取得桌面大小,并计算出程序窗口的位置

SystemParametersInfo(SPI_GETWORKAREA,0,&amp;R,0);

Left = R.Right - Width; //窗口左上角列坐标

Top = R.Bottom - Height; //窗口左上角行坐标

SetStayOnTop(this, true); //设置为总在最前面显示

}

2.相关设置为总在最顶层显示函数:

void TSpyForm::SetStayOnTop(TForm * Form, bool Value)

{

//设置窗口是否总在最前面显示

if ( Value )SetWindowPos(Form-&gt;Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);

else SetWindowPos(Form-&gt;Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);

}

3.定时器响应函数:

void __fastcall TSpyForm::TimerTimer(TObject *Sender)

{

//用定时器每隔0.1秒进行一切判断,以达到系统信息的实时显示

TPoint Pos;

HWND Handle;

char Buf;

GetCursorPos(&amp;Pos); //获得光标的位置

Handle = WindowFromPoint(Pos); //取得光标位置的窗口句柄

HandleEdit-&gt;Text = IntToStr((int)Handle); //显示窗口句柄

GetClassName(Handle, Buf, 1024); //获得类名

ClassEdit-&gt;Text = Buf; //显示类名

SendMessage(Handle, WM_GETTEXT, 1024, Integer(&amp;Buf)); //获得窗口信息

TextEdit-&gt;Text = Buf; //显示窗口信息

}

4.设置是否在最顶层显示:

void __fastcall TSpyForm::OnTopCheckBoxClick(TObject *Sender)

{

//设置最前面显示

SetStayOnTop(this, OnTopCheckBox-&gt;Checked);

}


(三)用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 &lt;Db.hpp&gt;

#include &lt;DbTables.hpp&gt;

#include &lt;DbCtrls.hpp&gt;

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

typedef void __fastcall (__closure *TDBTVSetImageIndexEvent)(System::TObject* Sender, int ID, int ParentID, int Level, int&amp; ImageIndex, int&amp; 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&amp; 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 &amp;Item);//Edit方法

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

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

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

virtual bool __fastcall CustomDrawItem(TTreeNode* Node, TCustomDrawState State, TCustomDrawStage Stage, bool &amp;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() &lt;&lt; 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-&gt;DataSource == NULL)//如果未指定数据源则抛出一个异常

throw Exception("Missing DataSource property.");

if (FDataLink-&gt;DataSource-&gt;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-&gt;DataSource)//如果指定值与当前值不一样,则

{

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

}

}

TDataSource* __fastcall TDBTreeView::GetDataSource()

{//获取数据源

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

}

void __fastcall TDBTreeView::ClearAllNodes()

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

for (int i=0; i&lt;this-&gt;Items-&gt;Count; i++)

delete (TDBTreeNodeData*)(Items-&gt;Item-&gt;Data);



this-&gt;Items-&gt;BeginUpdate();

this-&gt;Items-&gt;Clear();

this-&gt;Items-&gt;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-&gt;DatabaseName = ((TTable*)(FDataLink-&gt;DataSource-&gt;DataSet))-&gt;DatabaseName;

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

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



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

strSql += FParentIDField + "=:PID";



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

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

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



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



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

{

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

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

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



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

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

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

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

TreeNode-&gt;SelectedIndex = iSelectedIndex;



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

NodeData.ParentID = iParentID;

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



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

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

}



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



AQuery-&gt;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-&gt;DatabaseName = ((TTable*)(FDataLink-&gt;DataSource-&gt;DataSet))-&gt;DatabaseName;//设置数据库名

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

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



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

strSql += FParentIDField + "=:PID";



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

AQuery-&gt;ParamByName("PID")-&gt;AsInteger = ParentID;

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



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



while (!AQuery-&gt;Eof)//未到数据集尾部

{

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

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

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



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

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

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

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

TreeNode-&gt;SelectedIndex = iSelectedIndex;



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

NodeData.ParentID = iParentID;

AddDataToNode(TreeNode, NodeData);



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

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

}



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



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

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

}

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

{//追加数据到节点

TDBTreeNodeData* pData = new TDBTreeNodeData;

*pData = Data;

Node-&gt;Data = pData;

}

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

TDBTreeNodeData __fastcall TDBTreeView::GetNodeData(TTreeNode* Node)

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

return *(TDBTreeNodeData*)(Node-&gt;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) &amp;&amp; (FDataLink != NULL) &amp;&amp; (AComponent == DataSource))//如果操作为opRemove以及数据连接不为空且指定组件为数据源,则

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

}

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

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

{//编辑节点

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



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

{

TTreeNode* Node;

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

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

TDBTreeNodeData NodeData;

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

AnsiString strSql;



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

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



NodeData = GetNodeData(Node);

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



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

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

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

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

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

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

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



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

}

}

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

void __fastcall TDBTreeView::Expand(TTreeNode* Node)

{//展开指定节点

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



TDBTreeNodeData NodeData;

TTreeNode* ANode;



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

{

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

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

FillChildTreeNodes(NodeData.ID, ANode);

}

}

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

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

{

//键盘处理

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



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

{

/* Handle 'F2' key */

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

this-&gt;Selected-&gt;EditText();

}

}

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

void __fastcall TDBTreeView::Change(TTreeNode* Node)

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

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

static TTreeNode* OldNode = NULL;

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

TDBTreeNodeData NodeData;

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



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

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

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

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

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

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

ATable-&gt;GotoKey();

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

}

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

bool __fastcall TDBTreeView::CustomDrawItem(TTreeNode* Node, TCustomDrawState State, TCustomDrawStage Stage, bool &amp;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-&gt;Items-&gt;BeginUpdate();//开始更新DBTreeView

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

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

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

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

}



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

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

{//取得字段值

int i;

TDBTreeView* ADBTreeView;



ADBTreeView = (TDBTreeView*)GetComponent(0);

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

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

for (i = 0; i &lt; ADBTreeView-&gt;DataSource-&gt;DataSet-&gt;FieldCount; i ++)

Proc(ADBTreeView-&gt;DataSource-&gt;DataSet-&gt;Fields-&gt;Fields-&gt;FieldName);

}

else

{//否则抛出异常

throw Exception("Missing DataSource property.");

}

}

TTypeInfo* AnsiStringTypeInfo(void)

{

//定义类型信息

TTypeInfo* TypeInfo = new TTypeInfo;

TypeInfo-&gt;Name = "AnsiString";

TypeInfo-&gt;Kind = tkLString;

return TypeInfo;

}


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



用BCB设计DBTreeView组件小结

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

TTypeInfo* AnsiStringTypeInfo(void)

{

//定义类型信息

TTypeInfo* TypeInfo = new TTypeInfo;

TypeInfo-&gt;Name = "AnsiString";

TypeInfo-&gt;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 = {__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&amp; ImageIndex, int&amp; SelectedIndex);

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

Private:

TDBTVSetImageIndexEvent FonSetImageIndex;

__published:

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

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

private:

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

BEGIN_MESSAGE_MAP

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

END_MESSAGE_MAP(父类)


(四)动态显示任务栏图标

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>

在应用程序运行时,有时可能会因为要处理的数据量很大而暂时失去反应,这样给用户的感觉极其不好,影响了软件的更广泛应用。利用多线程技术固然可以解决这个问题,可这里我并想使用多线程,我想示范一下如何编制动态任务栏图标。

  我的编程思路是:通过设置Timer时钟控件使应用程序在规定的时间间隔内发送特定的消息,使任务栏图标不断更改,从而形成动画效果。实现方法为在应用程序的窗体中拖放几个Image控件,使他们装载相应的图画,几幅图画按顺序连续的被显示,就形成了动画。

  应用程序必须用发送消息的办法通知任务栏增加,删除,和修改图标。发送消息必须调用Shell_NotifyIcon。它的原形为:

  WINSHELLAPI BOLL WINAPI Shell_NotifyIcon(
  DWORD dwMessage, POINTIFYCONDATA pnid);

  第一个参数 dwMessage是发送消息的标志,可以选
  NIM_ADD // 往任务栏通知区添加图标
  NIM_DELETE //往任务栏通知区删除图标
  NIM_MODIFY //通知任务栏通知区修改图标

编制消息发送函数TrayMessage
bool __fastcall TForm1::TrayMessage(DWORD dwMessage)
{
NOTIFYICONDATA tnd;
PSTR pszTip;
pszTip = TipText();
tnd.cbSize= sizeof(NOTIFYICONDATA);
//结构的大小
tnd.uCallbackMessage = MYWM_NOTIFY;
//自定义回调消息,在头文件中声明
tnd.hWnd= Handle;
//接受回调消息的窗口句柄
tnd.uID = IDC_MYICON;
//图标标志号
tnd.uFlags= NIF_MESSAGE | NIF_ICON | NIF_TIP;
//指定以下三个参数哪个包含有效数据
if (dwMessage == NIM_MODIFY)
{
tnd.hIcon =
(HICON)IconHandle(); //取得图标句柄
if (pszTip)
lstrcpyn(tnd.szTip, pszTip,
sizeof(tnd.szTip));
else
tnd.szTip = '\0';
}
else
{
tnd.hIcon = NULL;
tnd.szTip = '\0';
}
return (Shell_NotifyIcon(dwMessage, &amp;tnd));
}
编制取得图标句柄的函数
HICON __fastcall TForm1::IconHandle(void)
{
if (n==1)
{ return (Image1- &gt;Picture-&gt;Icon- &gt;Handle);
//n是全局变量,1为显示Image1,0为Image2
}
else
{ return (Image2- &gt;Picture- &gt;Icon- &gt;Handle);
}
}
编制图标状态转换函数

void __fastcall TForm1::ToggleState(void)
{
if (n==1) //n为图标句柄锁,是全局变量,
1为显示Image1,0为Image2
{
n=n-1;
}
else
{
n=n+1;
}
TrayMessage(NIM_MODIFY);
//发送图标变换消息
}


  对Timer控件编制代码,设它的Interval属性为1000,即定时器每一秒响应一次。为 Ontimer事件键入代码:

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{ ToggleState( );
}


(五)用BCB编写多线程应用程序

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>


 
  随着Windows系统的全球性普及,多线程技术已越来越多地运用到许多软件设计中。使用多线程技术可全面提高应用程序的执行效率。以前为了实现多线程编程,基本上都是调用一系列的API函数,如CreateThread、ResumeThread等,不容易控制,还容易出错。在使用BCB以后,我才发现原来编写多线程程序也可以如此简单!BCB为我们提供了强大的TThread类,从而使得多线程编程变得非常简便易用。下面请跟我一起开始我们的BCB多线程编程之旅。
  1. 创建多线程程序:
  首先,我介绍一下BCB中编写多线程程序的具体步骤。
  在C++Builder IDE环境下选择菜单File|New,在New栏中选中Thread Object,按OK,接下来弹出输入框,输入TThread对象子类的名字NewThread,这样C++Builder自动为你创建了一个名为TNewThread的TThread子类。下面是TNewThread的部分源码:
 __fastcall NewThread::MyThread(bool CreateSuspended)
   : TThread(CreateSuspended)
  {//构造函数,可用来初始化一些信息
  }
  void __fastcall NewThread::Execute()
  {
   //多线程程序的核心,用来执行相关多线程操作
  }

BCB中的Execute()函数是我们要在线程中实现的任务代码所在地。使用多线程时,动态创建一个TNewThread 对象,在构造函数中使用Resume()方法,具体执行的代码使用Execute()方法重载的代码。如果想创建更多的线程,只需要创建需要数量的TNewThread 而已。
  通过以上步骤我们基本实现了在程序中如何创建一个线程,并使程序实现了多线程应用。但是,多线程应用的实现,并不是一件简单的工作,还需要考虑很多使多个线程能在系统中共存、互不影响的因素。比如,程序中公共变量的访问、资源的分配,如果处理不当,不仅线程会死锁陷入混乱,甚至可能会造成系统崩溃。总的来讲,在多线程编程中要注意共享对象和数据的处理,不能忽视。因此,下面我们要讲的就是多线程中常见问题:
  2. 多线程中VCL对象的使用
  我们都知道,C++Builder编程是建立在VCL类库的基础上的。在程序中经常需要访问VCL对象的属性和方法。不幸的是,VCL类库并不保证其中对象的属性和方法是线程访问安全的(Thread_safe),访问VCL对象的属性或调用其方法可能会访问到不被别的线程所保护的内存区域而产生错误。因此,TThread对象提供了一个Synchronize方法,当需要在线程中访问VCL对象属性或调用方法时,通过Synchronize方法来访问属性或调用方法就能避免冲突,使各个线程之间协调而不会产生意外的错误。如下所示:
  void __fastcall TNewThread::PushTheButton(void)
  
  {
   Button1-&gt;Click();
  }
  
  void __fastcall TNewThread::Execute()
  {
   ...
   Synchronize((TThreadMethod)PushTheButton);
   ...
  }
  对Button1-〉Click()方法的调用就是通过Synchronize()方法来实现的,它可以自动避免发生多线程访问冲突。在C++Builder中,虽然有一些VCL对象也是线程访问安全的(如TFont、TPen、TBrush等),可以不用Sychronize()方法对它们的属性方法进行访问调用以提高程序性能,但是,对于更多的无法确定的VCL对象,还是强烈建议使用Synchronize()方法确保程序的可靠性。
  3. 多线程中公共数据的使用
  程序设计中难免要在多个线程中共享数据或者对象。为了避免在多线程中因为同时访问了公共数据块而造成灾难性的后果,我们需要对公共数据块进行保护,直到一个线程对它的访问结束为止。这可以通过临界区域(Critical Section)的使用来实现,所幸的是在C++Builder中,给我们提供了一个TCriticalSection对象来进行临界区域的划定。该对象有两个方法,Acquire()和Release()。它设定的临界区域可以保证一次只有一个线程对该区域进行访问。如下例所示:
  class TNewThread : public TThread
  {
   ...
  private:
  TCriticalSection pLockX;
  int x;
  float y;
  ...
  };
  void __fastcall TNewThread::Execute()
  {
  ...
  pLockX-&gt;Acquire();
  x++;
  y=sin(x);
  pLockX-&gt;Release();
  ...
  }
  这样,对公共变量x,y的访问就通过全局TCriticalSection 对象保护起来,避免了多个线程同时访问的冲突。
  4. 多线程间的同步
  当程序中多个线程同时运行,难免要遇到使用同一系统资源,或者一个线程的运行要依赖另一个线程的完成等等,这样需要在线程间进行同步的问题。由于线程同时运行,无法从程序本身来决定运行的先后快慢,使得线程的同步看起来很难实现。所幸的是Windows系统是多任务操作系统,系统内核为我们提供了事件(Event)、Mutex、信号灯(semaphore)和计时器4种对象来控制线程间的同步。在C++Builder中,为我们提供了用于创建Event的TEvent 对象供我们使用。
  当程序中一个线程的运行要等待一项特定的操作的完成而不是等待一个特定的线程完成时,我们就可以很方便地用TEvent对象来实现这个目标。首先创建一个全局的TEvent对象作为所有线程可监测的标志。当一个线程完成某项特定的操作时,调用TEvent对象的SetEvent()方法,这样将设置这个标志,其他的线程可以通过监测这个标志获知操作的完成。相反,要取消这个标志,可以调用ResetEvent()方法。在需要等待操作完成的线程中使用WaitFor()方法,将一直等待这个标志被设置为止。注意WaitFor()方法的参数是等待标志设置的时间,一般用INFINITE表示无限等待事件的发生,如果其它线程运行有误,很容易使这个线程死住(等待一个永不发生的事件)。
  其实直接用Windows API函数也可以很方便地实现事件(Event)、信号(semaphore)控制技术。尤其是C++Builder,在调用Windows API方面有着其它语言无可比拟的优势。所用的函数主要有:CreateSemaphore()、CreateEvent()、WaitForSingleObject()、ReleaseSemaphore()、SetEvent()等等。

(六)在IE工具栏中添加按钮



浏览器IE是我们使用频率最高的软件之一,如果能将应用程序集成到IE中,必将极大地方便用户的操作以及提高软件的附加值。幸好Microsoft已为我们提供了相应的接口,这使得我们的工作变得简单而有趣。

一、系统分析

通过对注册表的分析、对比,可以发现,IE工具栏按钮的设置在注册表的位置:

HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\INTERNET EXPLORER\EXTENSIONS\

这样我们就可以通过以下的步骤来实现我们的目标:向IE工具栏中添加按钮

1. 在上述路径下添加一个主键,键名任意,比如设置为:5D13E8D2-850A-101B-ARC0-4210102A8DA7;

2. 在上述主键下添加以下几个字符串:

n BUTTONTEXT:按钮显示文本

n CLSID:标识符,必须设置为:{1FBA04EE-3024-11D2-8F1F-0000F87ABD16}

n DEFAULT VISIBLE:设置按钮是否被显示出来

n EXEC:单击按钮后要调用应用程序

n ICON:按钮图标

n HOTICON:按钮的浮动图标

二、设计流程

TRegistry rg = new TRegistry;

rg-&gt;RootKey = HKEY_LOCAL_MACHINE;

rg-&gt;OpenKey(“SOFTWARE\MICROSOFT\INTERNET EXPLORER\EXTENSIONS\{5D13E8D2-850A-101B-ARC0-4210102A8DA7}”,true);

rg-&gt;WriteString(“BUTTONTEXT”,”按钮名称”);

rg-&gt;WriteString(“CLSID”,”{1FBA04EE-3024-11D2-8F1F-0000F87ABD16}”);

rg-&gt;WriteString(“DEFAULT VISIBLE”,”YES”);

rg-&gt;WriteString(“EXEC”,”应用程序路径”);

rg-&gt;WriteString('”ICON”,”图标路径”);

rg-&gt;WriteString(“HOTICON”,”浮动图标路径”);

rg-&gt;CloseKey();

delete rg;
(七)如何设计系统环境监控程序

你想实时掌握系统资源吗?比如,当前窗口、磁盘及内存使用情况、设备及环境变量设置情况、正在运行的程序及开机启动程序等。通过我的系列介绍,相信你一定可以深入地了解这些信息,并可随意地通过编程来控制它。

一、如何获取当前窗口

这里需要一个API函数:EnumWindows。

函数功能:该函数枚举所有屏幕上的顶层窗口,办法是先将句柄传给每一个窗口,然后再传送给应用程序定义的回调函数。EnumThreadWindows函数继续到所有顶层窗口枚举完为止或回调函数返回FALSE为止函数原型:BOOL EnumWindows(WNDENUMPROC lpEnumFunc,LPARAM lParam);

参数:

lpEnumFunc:指向一个应用程序定义的回调函数指针,请参看EnumWindowsProc。

lPararm:指定一个传递给回调函数的应用程序定义值。

返回值:如果函数成功,返回值为非零;如果函数失败,返回值为零。若想获得更多错误信息,请调用GetLastError函数。

备注:EnumWindows函数不列举子窗口。

在循环体中调用这个函数比调用GetWindow函数更可靠。调用GetWindow函数中执行这个任务的应用程序可能会陷入死循环或指向一个已被销毁的窗口的句柄。


我编写的回调函数:bool __stdcall EnumProc(HWND hWnd,long lp);//详细定义如下:

bool __stdcall EnumProc(HWND hWnd,long lp)

{

if(hWnd==NULL) return false;//如果指定的窗口句柄为空,则退出
char title; //保存窗口标题
char hwndStr; //保存窗口句柄
char className;//保存类名
TListItem *mItem;//用来存储窗口信息:窗口标题、句柄及类名
GetWindowText(hWnd,title,60);//取得指定窗口句柄的窗口标题
if (AnsiString(title)!= "" &amp;&amp; AnsiString(title)!= "Default IME")

{//如果窗口标题不为空且不是输入法标题,则
mItem=MainForm-&gt;ListView1-&gt;Items-&gt;Add();//添加该窗口信息到列表中
sprintf(hwndStr,"%08x",hWnd);//将窗口句柄转化为16进制
mItem-&gt;Caption=AnsiString(title);
mItem-&gt;SubItems-&gt;Add(AnsiString(hwndStr));//添加窗口句柄
GetClassName(hWnd,className,60);//取得类名
mItem-&gt;SubItems-&gt;Add(AnsiString(className));//添加类名
}
return true;
}

获取当前窗口:EnumWindows((WNDENUMPROC)EnumProc,0);

《未完待续》


(八)TServerSocket和TClientSocket应用技巧

:nxyc_twz@163.com

在网络编程中,WinSocket API编程是最基本,也是最繁锁的部分。但是,如果你是采用C++Builder 5 作为编程平台,事情就变得简单的多了!通过我的介绍,相信你一定能快速掌握设计基于C/S体系的方法与技巧。
在BCB中,TServerSocket和TClientSocket涵盖了基本的WinSocket编程,其中TServerSocket作为服务器方使用,TClientSocket作为客户端使用,这两个组件本身并不提供Socket连接,但是他们都有一个Socket属性,这个属性才提供了Socket连接。下面就先向大家介绍一下这两个组件常用的方法属性,然后在通过一个例子来看看这两个组件的使用。
1)TServerSocket
名称 类型 说明
Socket TServerWinSocket 最重要的属性,提供Socket连接,事实上发送/接收数据都要靠这个属性.
Port int 要监听的端口,如果在Service属性中指定了服务类型,此属性将被忽略.
Service AnsiString 提供的服务,如HTTP、FTP等,如果在这里指定了服务类型,
Port将被忽略,因为各种服务都有特定的端口,如FTP:21、HTTP:80
ServerType TServerType 设置与客户连接的方式,取值为两个枚举常量stNonBlocking和
stThreadBlocking,stNonBlocking表示用非阻塞方式连接每一个客户
每个连接都在一个单独的线程中处理。并用OnClientRead()和
OnClientWrite()通知服务器端的Socker进行读写。stThreadBlocking
表示以阻塞方式连接客户,即以主动查询的方式可客户连接。
Active bool 激活服务,相当于调用Open()方法。

OnAccept事件当有客户请求连接时触发
OnClientRead事件通知服务器去读取有关信息。OnClientWrite与此类似。

2)TClientSocket
名称 类型 说明
Socket TClientWinSocket 同TServerSocket
Active bool 同TServerSocket
Address AnsiString 服务器的IP地址,如202.98.35.14
ClientType TClientType 与服务器连接方式,取值为两个枚举常量ctNonBlocking,tBlocking。
ctNonBlocking表示非阻塞方式,ctBlocking表示阻塞方式,详见上例。
Host AnsiString 要连接的主机名,如www.cpcw.com
Port int 同TServerSocket
Service AnsiString 同TServerSocket

OnConnect事件当连接时发生,OnConnecting、OnDisConnect与此类似
OnRead事件通知客户机去读取有关信息。OnWrite与此类似。

TServerSocket和TClientSocket只提供基本的服务器/客户机的连接,真正提供数据传输的是它们都有的属性Socket,它的类型分别是TServerWinSocket和TClientWinSocket,而TServerWinSocket和TClientWinSocket的父类都是TCustomWinSocket,下面我们就来看看TServerWinSocket和TClientWinSocket常用的属性和方法。

共同的属性方法(来源于TCustomWinSocket)
名称 类型 说明
Connected bool 检查是否连接成功
LocalAddress AnsiString 本地IP地址,与此类似LocalHost:本机域名,LocalPort:本机端口
RemoteAddress AnsiString 另一端的IP地址,与此类似RemoteHost:另一端域名,
RemotePort:另一端端口
SocketHandle int 只读,返回Socket对象的Windows句柄,调用WinSocket API函数会用到它。
Handle HWND Socket能够接受到的异步事件都是以Windows消息的形式发送给此句柄的。

Close()方法作为服务器,关闭所有连接;作为客户机,关闭自己与服务器的连接
SendText(AnsiString)方法发送一个字符串,
SendBuf(void* buff,int count)发送缓冲区buff中的count个字节,返回实际发送的字节数
SendStream(TStream* AStream)发送一个流到Socket中。
ReceiveText()从Socket中读取并返回一个字符串。
ReceiveLength()从Socket读取数据需多少字节的缓冲区。
ReceiveBuf(void* buff,int count)从Socket中读取count字节的数据到buff。

TClientWinSocket
TClientWinSocket只增加了一个ClientType属性,
用于决定与服务器的连接类型(参见TClientSocket-&gt;ClientType)。

TServerWinSocket

名称 类型说明
ServerType  服务类型,参见TServerSocket-&gt;ServerType。
ActiveConnection int只读,返回当前活动的连接数。
Connection TCustomWinSocket数组,索引n表示第n+1个连接,如Connection表示第一个连接。

有了这些知识,我们就可以完成一些基本的WinSocket编程了,下面就结合一个简单的闲聊程序来看看具体的应用。
首先在窗体上放置以下VCL组件,并修改相应属性:
类型 Name 属性 Caption/Text 说明
TCheckBox ckListen 监听当选取时,本程序作为服务器
TCheckBox ckConnect 连接当选取时,本程序作为客户机
TEdit edName 无名氏闲聊时所用的名字。
TBitBtn bbtSave&amp;S 保存单击时保存谈话内容
TBitBtn bbtClose&amp;C 关闭单击时关闭此窗口(设置Kind=bkClose)
TEdit edTalk  在此输入谈话内容
TMemo mmTalk  在此显示谈话内容
TServerSocket ServerSocket1 作服务器时使用(设置Port=2222)
TClientSocket ClientSocket1 作为客户时使用(设置Port=2222)
TSaveDialog sdTalk  保存文件时的选项(设置DefaultExt="*.txt",Filter=文本文件(*.TXT)
|*.txt|所有文件(*.*)|*.*)。
TStatusBar StatusBar1  用于显示一些提示信息,只要在属性"Pannels"中加一栏即可

程序作为服务器的设置:
当单击"监听"时,如果没有监听则开始监听,在提示栏中显示"监听",并把"连接"这个复选框无效。如果已经监听,则取消监听,并使"连接"这个复选框有效。所以,在ckListen的OnClick事件中加入以下代码:


if(ServerSocket1-&gt;Active)
{
ServerSocket1-&gt;Active=false;
ckListen-&gt;Checked=false;
StatusBar1-&gt;Panels-&gt;Items-&gt;Text="";
}
else
{
ServerSocket1-&gt;Active=true;
ckListen-&gt;Checked=true;
ClientSocket1-&gt;Active=false;
StatusBar1-&gt;Panels-&gt;Items-&gt;Text="监听..." ;
}
ckConnect-&gt;Enabled=!(ckListen-&gt;Checked);

当有客户加入时,向所有的客户发出通知:并在自已的mmTalk加入此消息:所以在ServerSocket1的OnAccept事件中加入如下代码:

int i;
AnsiString str1="服务器消息:"+Socket-&gt;RemoteHost+"加入";
for(i=0;i&lt;ServerSocket1-&gt;Socket-&gt;ActiveConnections;i++)
ServerSocket1-&gt;Socket-&gt;Connections-&gt;SendText("服务器消息:"+Socket-&gt;RemoteHost+"加入");
StatusBar1-&gt;Panels-&gt;Items-&gt;Text=str1;
mmTalk-&gt;Lines-&gt;Add(str1);

当客户机通知服务器读信息时,首先读出字符串,然后把读到的字符串发送到每一台连接的客户,并在自已的mmTalk中加入客户发送来的字符串。所以,在TServerSocket的OnClientRead事件中加入以下代码:

AnsiString str1=Socket-&gt;ReceiveText();
mmTalk-&gt;Lines-&gt;Add(str1);
int i;
for(i=0;i&lt;ServerSocket1-&gt;Socket-&gt;ActiveConnections;i++)
ServerSocket1-&gt;Socket-&gt;Connections-&gt;SendText(str1);

程序作为客户机的设置:
当单击"连接"时,如果还未连接,则询问要连接的主机,然后连接之,屏蔽"监听";如果已经连接,则断开连接。"监听"使能。所以,在ckConnect的OnClick事件中加入以下代码:

if(ClientSocket1-&gt;Active)
{
ClientSocket1-&gt;Active=false;
ckConnect-&gt;Checked =false;
}
else
{
AnsiString Server="localhost";
if(InputQuery("连接","请输入要连接的主机地址:",Server))
{
ClientSocket1-&gt;Host=Server;
ClientSocket1-&gt;Active=true;
ckConnect-&gt;Checked =true;
}
}
ckListen-&gt;Enabled= !(ckConnect-&gt;Checked);

当连接服务器成功时,在状态栏中显示此信息,所以,在ClientSocket1的ClientSocket1Connect中加入:

StatusBar1-&gt;Panels-&gt;Items-&gt;Text ="连接到主机:"+Socket-&gt;RemoteHost;
当服务器发送字符串来时,把它加入mmTalk中,但如果本字符串就是自已发送的(因为服务器会把收到的消息发给每一客户),为条信息就是重复的,所以,要比较mmTalk中最后两条信息是否相同,如果相同,则删除重复信息。代码如下:

mmTalk-&gt;Lines-&gt;Add(Socket-&gt;ReceiveText());
int i=mmTalk-&gt;Lines-&gt;Count-1;
if(mmTalk-&gt;Lines-&gt;Strings==mmTalk-&gt;Lines-&gt;Strings)
mmTalk-&gt;Lines-&gt;Delete(i);

公用部分
当在edTalk输入交谈内容,按回车键表示输入完成,此时把交谈内容发送出去并清除edTalk的内容。在发送信息时,要看本程序是作为服务器还是客户机,如果是服务器则把信息发送到每一个客户;如果是作为客户则把信息发送到服务器。代码如下:

if(Key==13)
{
mmTalk-&gt;Lines-&gt;Add(edName-&gt;Text+":"+edTalk-&gt;Text);
if(ckListen-&gt;Enabled&amp;&amp;ckConnect-&gt;Enabled==false)
//"监听"有效,"连接"无效。表示是服务器
{
int i;
for(i=0;i&lt;ServerSocket1-&gt;Socket-&gt;ActiveConnections;i++)
ServerSocket1-&gt;Socket-&gt;Connections-&gt;SendText(edName-&gt;Text+":"+edTalk-&gt;Text);
}
else
{
ClientSocket1-&gt;Socket-&gt;SendText(edName-&gt;Text+":"+edTalk-&gt;Text);
}
edTalk-&gt;Text="";
}

mmTalk的内容不可能永远增加,所以当它有100行时就清空它,在mmTalk的OnChange事件中检查:

if(mmTalk-&gt;Lines-&gt;Count&gt;=100)mmTalk-&gt;Lines-&gt;Clear();

当然你也可以双击mmTalk来清空它,在mmTalk的OnDblClick事件中加入:

mmTalk-&gt;Lines-&gt;Clear();
当你觉得谈话的内容很有意思,你可以单击bbtSave打开保存对话框设置保存特性,所以在bbtSave的onClick中加入代码:

if(sdTalk-&gt;Execute())
mmTalk-&gt;Lines-&gt;SaveToFile(sdTalk-&gt;FileName);

我们的闲聊程序完成了,在局域网中试试吧?如果你只有一台机器,客户程序在连接时服务器时输入localhost或你机器的名字就可以了。

(九)读写端口的两种技巧

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>

在C++Builder中,不能够使用Turbo C中的outputb和inputb端口读写函数。但我们可以有另外两种办法实现这个功能。一种为内嵌汇编语言,另一种为使用__emit__函数。

1 通过内嵌汇编语言实现端口的读写

在C++Builder中,汇编语句必须被包含在以关键字asm为起始的一对大括号中:

asm {

汇编语句1

……

}

利用内嵌汇编语言编制端口输出函数如下:

void OutPort(unsigned short port,unsigned char value)//port参数为输出端口地址,value参数为输出值
{
asm{
mov dx , port //把端口地址送到处理器DX寄存器中
mov al , value // 把value 送到处理器AL寄存器中
out dx , al // 把AL寄存器中的值送到端口
};

}

该函数将无符号字符型8位的数据value写入地址为port的端口上,port的数据类型是unsigned short ,16位无符号短整形。
利用内嵌汇编语言编制端口输入函数如下:

unsigned char InPort(unsigned short port)//port参数为输入端口地址,返回为输入值
{
unsigned char value ;
 asm{
mov dx , port // 把端口地址送到处理器DX寄存器中
in al, dx // 从DX指定端口中将一数据送到AL寄存器中
mov ind , value // 把AL寄存器中的值赋给value
};
return value; //返回端口数据
}

函数InPort从地址为port的端口读入一个无符号8位的字符型数据,其其参数只一个,即端口号。返回的数据为unsigned char类型的,为从端口读取的值。


2 通过__emit__函数实现端口的读写

__emit__ 函数一般极少用到。其用法如下:

void _ _emit_ _(argument, . . .);

该函数为C++Builder 的一个内部函数,调用的参数为机器语言指令。它在编译的时侯,将机器语言指令直接嵌入目标码中,不必借助于汇编语言和汇编编译程序。
如果想使用__emit__ 函数,必须熟悉80x86处理器的机器语言指令。如果调用的参数是错误机器语言指令,则程序将非正常运行,并很容易导致死机。

利用__emit__函数编制端口输出函数如下:

void OutPort(unsigned short port,unsigned char value)//port参数为输出端口地址,value参数为输出值
{
__emit__(0x8b,0x95,&amp;port); // 把端口地址送到处理器EDX寄存器中
 __emit__(0x8a,0x85,&amp;value); // 把value 送到处理器AL寄存器中
 __emit__(0x66,0xee); // 把AL寄存器中的值送到端口

}

利用__emit__函数编制端口输入函数如下:

unsigned char InPort(unsigned short port)//port参数为输入端口地址,返回为输入值
{
unsigned char value ;
 __emit__(0x8b,0x95,&amp;port) ; // 把端口地址送到处理器DX寄存器中
 __emit__(0x66,0xec); // 从DX指定端口中将一数据送到AL寄存器中
 __emit__(0x88,0x85,&amp;value); // 把AL寄存器中的值赋给value
return value; //返回端口数据
}

由这两种方法所编制的函数注释可以看出,它们每一句的功能都是一样的,只是一个是嵌入了汇编语言,另一个是直接使用机器语言。


3 应用举例

在C++Builder中,通过File/New Application菜单新建一工程。

在表单中加两个Button控件,Caption分别为“写端口”和“读端口”。

将第一种方法所编制的OutPort和InPort函数拷贝到表单的头文件中,并把这两个函数作为表单类的在表单类的公有成员函数。

分别双击两个Button控件,产生OnClick事件函数。在单元文件的.cpp文件添加如下代码:

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

void __fastcall TForm1::Button1Click(TObject *Sender)

{
OutPort(0x378,0x00);//向地址为378H的端口输出数据
OutPort(0x379,0x00);//向地址为379H的端口输出数据
}

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

void __fastcall TForm1::Button2Click(TObject *Sender)

{
int value;
value=InPort(0x37a);//从地址为37aH的端口读入数据
}

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


(十)如何实现控件数组




在C++ Builder中,没有提供像VB中控件数组的功能,很令遗憾。经过一番琢摸,终于解决了这个问题。技巧不敢独享,奉献出来供大家交流。


在VB中,控件数组可以 :


允许多个控件共享同一个事件句柄
提供了运行期间增加一个控件的机制
提供了一种方便的组合控件的方法。
前两项在C++ Builder中早已实现,而且CB更有一个优点。即不同类型的控件可以使用相同的句柄(只需在相关控件的Object Inspector窗口中的EVENT事件设置即可)。
   C++ Builder中使用了Tlist类对象来组合控件数组,与VB控件数组元素必须为同一类型控件相比较,C++ Builder中的Tlist类对象可以组合任意类型的控件而不必强求同一种类,这样就大大地方便了程序开发者。例如可以把在不同Panel面板控件上的所有控件组合为一个控件数组。
   在具体的开发实践中,我采用Tlist类对象创建、维护了多类型的控件数组。其实现原理与方法详见以下的原程序代码。实例程序实现了动态创建一个包含8个TEdit类型控件和4个TImage类型控件的控件数组,在程序运行中对所创建的控件进行修改、维护的功能。
   1、创建一个新的工程文件(New Application),在Form1上放置两个TPanel类型的Panel1和Panel2,调整大小合适,再在窗体下方放置四个TButton类型Button1,Button2,Button3,Button4,设置控件属性如下:Button1-&gt;Caption="新建控件数组",Button2-&gt;Caption="改变控件位置", Button3-&gt;Caption="还原到原位置", Button4-&gt;Caption="退出";Button2-&gt;Enabled=false,Button3-&gt;Enabled=false。
  2、在文件Unit1.H中加入以下声明:
  class TForm1 : public TForm
   {
   published: // IDE-managed Components
   TPanel *Panel1;
   TPanel *Panel2;
   TButton *Button1;
   TButton *Button2;
   TButton *Button3;
   TButton *Button4;
   private: // User declarations
   TList *MyVCL;
   public: // User declarations
   __fastcall TForm1(TComponent* Owner);
   virtual __fastcall ~TForm1( );
  };
  3、切换到工程的Form界面,双击工程的主界面Form,创建一个OnCreate事件句柄,在文件Unit1.CPP中加入以下代码:
  void __fastcall TForm1::FormCreate(TObject *Sender)
  {
   MyVCL = new TList;//创建TList对象
  }
  将TForm1析构函数加入到文件Unit1.CPP中:
  __fastcall TForm1::~TForm1()
  {
   delete MyVCL; //删除TList对象
  }
   4、双击标签(Caption)为"创建控件数组"的按钮,创建一个OnClick事件句柄,添加以下代码到OnClick事件句柄中:
  void __fastcall TForm1::Button1Click(TObject *Sender)
  {
   //创建新的控件,调整其位置,并加入到MyVcl(TList 类)之中
   int temptop=5;
   for (int i=0;i&lt;4;i++)

{
   TEdit *EditNow = new TEdit(this);
    EditNow-&gt;Parent=Panel1;
   EditNow-&gt;Text= IntToStr(i);
   EditNow-&gt;ReadOnly=true;
   EditNow-&gt;Top=temptop;
   EditNow-&gt;Height=24;
   EditNow-&gt;Width=24;
   EditNow-&gt;Left=10;
   MyVCL-&gt;Add(EditNow); //加入到控件数组中
   TImage *ImageOff= new TImage(this);
    ImageOff-&gt;Parent=Panel1;
   ImageOff-&gt;Picture-&gt;LoadFromFile("None.BMP");
   ImageOff-&gt;Top=temptop;
   ImageOff-&gt;Height=24;
   ImageOff-&gt;Width=24;
   ImageOff-&gt;Left=EditNow-&gt;Left+EditNow-&gt;Width;
   MyVCL-&gt;Add(ImageOff); //加入到控件数组中
   TEdit *EditStatus = new TEdit(this);
   EditStatus-&gt;Parent=Panel1;
   EditStatus-&gt;Font-&gt;Name = "Arial";
   EditStatus-&gt;Font-&gt;Size = 12;
   EditStatus-&gt;Text="禁止访问";
    EditStatus-&gt;ReadOnly=true;
   EditStatus-&gt;Top=temptop;
   EditStatus-&gt;Height=24;
   EditStatus-&gt;Width=80;
   EditStatus-&gt;Left= ImageOff-&gt;Left+ImageOff-&gt;Width;
   MyVCL-&gt;Add(EditStatus); //加入到控件数组中
    temptop=temptop+24+5;
   }
   Button1-&gt;Enabled=false;
   Button2-&gt;Enabled=true;
   }
  5、同4所示方法,依次双击标签的标题(Caption)为"改变控件位置"、 "还原到原位置"、 "退出"的按钮,创建对应的OnClick事件句柄,添加以下代码到对应的OnClick事件句柄中:
  void __fastcall TForm1::Button2Click(TObject *Sender)
  {
   for (int i=0;i&lt;4;i++)
   ((TImage*)MyVCL-&gt;Items)-&gt;Parent=Panel2;
   Button2-&gt;Enabled=false;
   Button3-&gt;Enabled=true;
  }
  void __fastcall TForm1::Button3Click(TObject *Sender)
  {
   for (int i=0;i&lt;4;i++)
   ((TImage*)MyVCL-&gt;Items)-&gt;Parent=Panel1;
   Button3-&gt;Enabled=false;
   Button2-&gt;Enabled=true;
  }
  void __fastcall TForm1::Button4Click(TObject *Sender)
  {
   Close(); //关闭窗体
  }
  由上所述,实际的代码大多只是用于设定控件位置和基本属性,真正实现控件数组功能的代码并不太多,也不复杂,而且十分的灵活;需要注意的是使用TList类对象组合中的各项控件之前,必须先将其强制转换为一个对象指针以指明其类型,才能对其属性进行修改/赋值操作。

(十)用Sender参数实现代码重用

<a href="mailtnxyc_twz@163.com" target="_blank" >nxyc_twz@163.com</A>

面向对象的编程工具的特点之一就是要提高代码重用性(Reuse),BCB当然可以实现这一功能。我们都知道,在BCB中,大部分程序代码都直接或间接的对应着一个事件,此程序称为事件处理句柄,它实际上就是一个过程。从应用程序的工程到窗口、组件和程序,BCB强调的是其开发过程中每一层次的重用性,可以充分利用已编写过的代码来减少工作量,更会使你的程序变得优美。代码段间的共享都跟发生该事件的控件有关,需要根据控件类型做出相应的处理,这时就要用到Sender参数。

每个函数的开头都有形如:
void _fastcall Tform1::Button1Click(TObject *Sender)

其中的Sender是一个Tobject类型的参数,它告诉BCB哪个控件接收到这个事件并调用相应的处理过程。我们可以编写一个单一的事件处理句柄,通过Sender参数和if语句或者case语句配合,来处理多个组件。在Delphi中可以用IS来测试Sender类型,或者用AS进行类型转换,BCB我们只在用dynamic_cast来进行上面两个工作,下面把dynamic_cast的用法说明一下。

dynamic_cast 可以把某种对象强制转成另一个类,类型转换成功则返回一个值是0的指针,失败则丢出一个异常处理信息:Bad_cast,但你放心不会导致系统死机,所以可以放心使用。其程式:
dynamic_cast &lt;T&gt; (ptr)
T参数一定要是一个指针、void 、或是已经定义过的类,而ptr参数则必须是一个指针(pointer) 或是一个引用(reference)。如果T的类型是void,那么ptr则是一个可以访问最下面类里的任何成员,当然这样的类将不可以是基础类。

1.进行判断
我们用dynamic_case来测试Sender,以便找到调用这个事件的处理句柄或组件的类型。如,并窗口编辑框和标签的Click事件的处理句柄都指向窗口的xxx函数,编辑框和标签对Click事件有不同的反应:
void _fastcall TForm1::xxx(Tobject *Sender)
{
if(dynamic_cast&lt;TEdit *&gt;(Sender)
showmessage(“This is a editbox”);
if(dynamic_cast&lt;TLabel *&gt;(Sender)
showmessage(“This is a label”);
}

2.强制进行类型转换
将若干继承同一父类的子类强制转换成该父类。如窗口中有一个TEdit类控件和一个TMemo控件,它们实际上都继承于TCustomEdit类,如果你要为二者的某一事件提供同样的处理,可以将二者事件句柄都指向自定义的函数yyy:
void _fastcall TForm1::yyy(Tobject *Sender)
{
dynamic_cast&lt;TCustomEdit *&gt;(Sender).text=”This is some demo text”;
}
在这里,先把TEdit类和TMemo类均强制转换成TCustomEdit类,再对其父类的属性进行赋值。
使用Sender参数可以通过单一函数段处理多类组件,真正体现了BCB的面向对象的重用性。</DIV>
页: [1]
查看完整版本: [转帖]C++ Builder 高手进阶