C++Builder使用经验谈
【打印文章】
C++Builder3.0是Borland公司(现已更名为Insprise)于1998年推出的新一代基于C语言的RAD开发工具。C++Builder3.0的问世,对广大爱好C语言的用户来说不啻是个福音。因为以往在Windows下,没有一种真正基于C语言的可视化编程语言。你如果想用VB或Delphi这一类可视化编程语言去编程,你就不得不去重温一遍Basic或Pascal语言,没有了像C语言一样可以灵活应用的指针,没有了"++"、"――"这样一类可爱的运算,总之一切使用起来都不如C语言一样得心应手。现在这一切烦恼都不复存在了。C++Builder3.0不仅支持传统的C语言,也支持Borland的OWL和Microsoft的MFC。可以这样说,C++Builder3.0是目前Windows下功能最为强大的C语言编译器。由于C++Builder3.0问世不久,有关资料不是很多,下面结合笔者的使用情况,谈谈几点经验、体会。
一、动态调用窗体Form
在缺省情况下,由File/NewForm生成添加入项目文件中的窗体都具有"AutoCreate"(自动创建)的特性。即只要程序运行,该窗体就存在于内存中了,不管当前它是否被调用。具有这种特性的窗体一般适用于窗体属性比较固定、经常被调用的情况。其优点是速度快,缺点是占用内存。在实际程序设计中,会遇见大量类似对话框功能的窗体,它们用于显示状态或输入信息,仅须在程序中调用一下,完成其功能就行了,无需常驻内存。这时可以通过选择Project/Options/Forms,将"Auto--Createforms"栏中相应的窗体,如Form1,用">"键移动到"Availableforms"栏中,并在程序需调用该窗体处,加入下列语句:
TForm1*myform=newTForm1(this);
myform->ShowModal();
deletemyform;
窗体Form1仅是在需要调用时才调入内存,调用完成后,即用delete清除出内存。这样可减少程序对内存资源的占用。
二、遍历窗体控件的方法
要访问或修改窗体上的控件,方法很简单,以TEdit为例子:
Edit1->Text="";
Edit2->Text="";
但如果窗体上有十来个像Edit1这样的控件,需要进行相同的初始化,用上面的方法一个一个地进行,岂不麻烦!所以有必要掌握遍历窗体控件的方法。在介绍该方法之前,让我们先了解一下窗体Form的Components和Controls属性。参见表一。
表一
属性类型说明
ComponentCountInt目前Form上各类
控件的总数
ComponentsTCompont*目前Form上指向
所有控件的数组
ControlCountInt目前Form上某一子
区域上各类控件的总数
ControlsTControl*目前Form上指向某一子
区域上所有控件的数组
以图一为例(图略)说明,Form1的ComponentCount=6,而Panel1的ControlCount=4.,
其中:数组对象
Components[0]Panel1
Components[1]Label1
Components[2]Edit1
Components[3]Label2
Components[4]Edit2
Components[5]Button1
数组对象
Controls[0]Label1
Controls[1]Edit1
Controls[2]Label2
Controls[3]Edit2
下面这段代码完成了对Panel1上所有TEdit控件的遍历初始化。读者稍加修改,即可对其它控件进行遍历。这里有一个小技巧,我们把需要进行初始化的控件放置在了一Panel1上,与不需要初始化的控件区分开来,这样便于编程。
AnsiStringnamestring="TEdit";
for(inti=1;iControlCount;i++)
{
if(Panel1->Controls[i]->
ClassNameIs(namestring))
{
TEdit*p=dynamic_cast
(Panel1->Controls[i]);
P->Text="";
}
}
三、用Enter键控制焦点切换的方法
在Windows环境下,要使一个控件取得焦点,可在该控件上用鼠标单击一下,或按Tab键将焦点移至该控件上。这种控制焦点切换的方法有时不符合用户的习惯。就图一而言,用户就希望用Enter键,控制焦点由Edit1切换到Edit2。要实现这样的功能需借助WinAPI函数SendMessage来完成。方法是:先设Form1的KeyPreview属性为true,然后在Form1的OnKeyPress事件中加入如下的代码。这样,用户就可以通过按Enter,键控制焦点按定义好的Taborder顺序来移动了!
void__fastcallTForm1::
FormKeyPress(TObject*Sender,char&Key)
{
if(Key==VK_RETURN)
{
SendMessage(this->Handle,WM_NEXTDLGCTL,0,0);
Key=0;
}
}
四、为TStringGrid的文字加上颜色
----TStringGrid是C++Builder提供给用户的一种字符网格控件。美中不足的是,它没有提供分别修改各单元字体颜色、大小的方法。其实要为TStringGrid实现这样功能,只需在程序中稍加处理就行了。方法是自定义一个二维数组cellbuf,它的下标与网格单元列行一一对应,用于存放各网格单元的颜色、文字等信息。
structCellStru
{
AnsiStringmsg;//文字信息
TColorcolor;//文字颜色
};
CellStrucellbuf[MAXCOL][MAXROW];
----初始化cellbuf后,再在字符网格控件StringGrid1的OnDrawCell响应事件中,加入如下的代码即可。
void__fastcallTForm1::StringGrid1DrawCell
(TObject*Sender,intCol,
intRow,TRect&Rect,TGridDrawStateState)
{
StringGrid1->Canvas->Font->
Color=cellbuf[Col][Row].color;
StringGrid1->Canvas->TextOut(Rect.Left+3,
Rect.Top+3,cellbuf[Col][Row].msg);
}
五、软件封面的实现
----现代软件设计的流行做法是,在程序运行完成初始化之前,先调用一幅画面做为封面,通常是1/4屏幕大小,显示一下软件的名称、作者、版本等信息。要用C++Builder实现这样的功能,方法很简单:①自定义一窗体类TSplashForm,将其设置成"透明窗口",即BorderIcons下的所有选项均置成false,BorderStyle=bsNone,FormStyle=fsStayOnTop,Position=poScreenCenter;②在TSplashForm窗体上放置一TPanel(相当于图形的镜框);③在TPanel上放置一TImage控件,调入所需要的图形;④对WinMain函数稍加修改,加入如下所示代码即可。需要指出的是,这段代码通过函数FindWindow,搜索内存中是否有窗口标题为"Demo"应用程序存在,若存在,则退出程序的运行。该功能可防止程序的再次运行。在某些场合这样设计是必须的。
WINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
try
{
if(FindWindow(NULL,"Demo")!=0)
{
Application->MessageBox
("程序已经运行!","警告",MB_ICONSTOP);
return0;
}
TSplashForm*splash=newTSplashForm(Application);
splash->Show();
splash->Update();
Application->Initialize();
Application->CreateForm(__classid(TForm1),&Form1);
splash->Close();
deletesplash;
Application->Run();
}
catch(Exception&exception)
{
Application->ShowException(&exception);
}
return0;
}
六、如何永久清除DBF中的已被删除的记录
用table->Delete()删除的DBF记录,并没有真正从DBF数据库中被删除,而仅仅是做上了一个删除标记。如何实现类似dBase中的Pack命令的功能呢?请看下面的代码。 table->Close();
for(;;)
try
{
table->Exclusive=true;
table->Open();
break;
}
catch(...)
{
}
if(DbiPackTable(table->DBHandle,table->
Handle,NULL,szDBASE,true)!=DBIERR_NONE)
Application->MessageBox("不能删除记录",
"错误",
MB_ICONSTOP);
七、I/O端口读写的实现
细心的读者会发现,C++Builder不再支持如inportb()、outportb()一类I/O端口读写指令了。准确地说,在Windows环境下,BorlandC++仅支持16位应用程序的端口操作,对32位应用程序的端口操作不再支持,而C++Builder开发出来的程序是32位的。我个人以为,这是C++Builder设计者的败笔。因为PC机中,I/O地址空间与内存地址空间从来都是各自独立的。看看Delphi,不就通过Port数组实现了对I/O端口的访问了吗?搞不清楚为什么C++Builder就没有提供类似的机制?下面这几个函数是笔者从网上淘下来的,经过验证,在Windows95环境下,的确可实现对I/O端口的读写。读者可以借鉴使用。
voidoutportb(unsignedshort
intport,unsignedcharvalue)
{
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//moval,*(&value);
__emit__(0x8a,0x85,&value);
//outdx,al;
__emit__(0x66,0xee);
}
voidoutportw(unsignedshort
intport,unsignedshortintvalue)
{
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//movax,*(&value);
__emit__(0x66,0x8b,0x85,&value);
//outdx,ax;
__emit__(0xef);
}
unsignedcharinportb(unsignedshortintport)
{
unsignedcharvalue;
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//inal,dx;
__emit__(0x66,0xec);
//mov*(&value),al;
__emit__(0x88,0x85,&value);
returnvalue;
}
unsignedshortintinportw(unsignedshortintport)
{
unsignedshortintvalue;
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//inax,dx
__emit__(0xed);
//mov*(&value),ax
__emit__(0x66,0x89,0x85,&value);
returnvalue;
}
八、软件的分发
在Windows下开发的应用程序一般都比较庞大,程序的运行往往离不开一大堆不知名的系统DLL文件。为了生成能脱离C++Builder环境、独立运行的应用程序,读者须对编译器进行一定的设置。方法是:置Project/Option/Packages/Runwithruntimepackages为Disable,置Project/Option/Linker/UsesdynamicRTL为Disable,重新编译一遍程序,这样生成的EXE文件就可以脱离C++Builder环境运行了。但如果你的程序中应用了数据库,仅有上述的操作是不够的--因为,你还得安装BDE(BorlandDatabaseEngineer)。BDE的安装比较麻烦,读者最好是用C++Builder3.0附带的InstallShieldExpress来制作安装盘,把应用程序和BDE打包在一起。如果找不到,也可用Delphi3.0附带的InstallShieldExpress来制作。InstallShield的使用方法,限于篇幅,不再介绍。有条件的读者可上网查到有关资料。
一、动态调用窗体Form
在缺省情况下,由File/NewForm生成添加入项目文件中的窗体都具有"AutoCreate"(自动创建)的特性。即只要程序运行,该窗体就存在于内存中了,不管当前它是否被调用。具有这种特性的窗体一般适用于窗体属性比较固定、经常被调用的情况。其优点是速度快,缺点是占用内存。在实际程序设计中,会遇见大量类似对话框功能的窗体,它们用于显示状态或输入信息,仅须在程序中调用一下,完成其功能就行了,无需常驻内存。这时可以通过选择Project/Options/Forms,将"Auto--Createforms"栏中相应的窗体,如Form1,用">"键移动到"Availableforms"栏中,并在程序需调用该窗体处,加入下列语句:
TForm1*myform=newTForm1(this);
myform->ShowModal();
deletemyform;
窗体Form1仅是在需要调用时才调入内存,调用完成后,即用delete清除出内存。这样可减少程序对内存资源的占用。
二、遍历窗体控件的方法
要访问或修改窗体上的控件,方法很简单,以TEdit为例子:
Edit1->Text="";
Edit2->Text="";
但如果窗体上有十来个像Edit1这样的控件,需要进行相同的初始化,用上面的方法一个一个地进行,岂不麻烦!所以有必要掌握遍历窗体控件的方法。在介绍该方法之前,让我们先了解一下窗体Form的Components和Controls属性。参见表一。
表一
属性类型说明
ComponentCountInt目前Form上各类
控件的总数
ComponentsTCompont*目前Form上指向
所有控件的数组
ControlCountInt目前Form上某一子
区域上各类控件的总数
ControlsTControl*目前Form上指向某一子
区域上所有控件的数组
以图一为例(图略)说明,Form1的ComponentCount=6,而Panel1的ControlCount=4.,
其中:数组对象
Components[0]Panel1
Components[1]Label1
Components[2]Edit1
Components[3]Label2
Components[4]Edit2
Components[5]Button1
数组对象
Controls[0]Label1
Controls[1]Edit1
Controls[2]Label2
Controls[3]Edit2
下面这段代码完成了对Panel1上所有TEdit控件的遍历初始化。读者稍加修改,即可对其它控件进行遍历。这里有一个小技巧,我们把需要进行初始化的控件放置在了一Panel1上,与不需要初始化的控件区分开来,这样便于编程。
AnsiStringnamestring="TEdit";
for(inti=1;iControlCount;i++)
{
if(Panel1->Controls[i]->
ClassNameIs(namestring))
{
TEdit*p=dynamic_cast
(Panel1->Controls[i]);
P->Text="";
}
}
三、用Enter键控制焦点切换的方法
在Windows环境下,要使一个控件取得焦点,可在该控件上用鼠标单击一下,或按Tab键将焦点移至该控件上。这种控制焦点切换的方法有时不符合用户的习惯。就图一而言,用户就希望用Enter键,控制焦点由Edit1切换到Edit2。要实现这样的功能需借助WinAPI函数SendMessage来完成。方法是:先设Form1的KeyPreview属性为true,然后在Form1的OnKeyPress事件中加入如下的代码。这样,用户就可以通过按Enter,键控制焦点按定义好的Taborder顺序来移动了!
void__fastcallTForm1::
FormKeyPress(TObject*Sender,char&Key)
{
if(Key==VK_RETURN)
{
SendMessage(this->Handle,WM_NEXTDLGCTL,0,0);
Key=0;
}
}
四、为TStringGrid的文字加上颜色
----TStringGrid是C++Builder提供给用户的一种字符网格控件。美中不足的是,它没有提供分别修改各单元字体颜色、大小的方法。其实要为TStringGrid实现这样功能,只需在程序中稍加处理就行了。方法是自定义一个二维数组cellbuf,它的下标与网格单元列行一一对应,用于存放各网格单元的颜色、文字等信息。
structCellStru
{
AnsiStringmsg;//文字信息
TColorcolor;//文字颜色
};
CellStrucellbuf[MAXCOL][MAXROW];
----初始化cellbuf后,再在字符网格控件StringGrid1的OnDrawCell响应事件中,加入如下的代码即可。
void__fastcallTForm1::StringGrid1DrawCell
(TObject*Sender,intCol,
intRow,TRect&Rect,TGridDrawStateState)
{
StringGrid1->Canvas->Font->
Color=cellbuf[Col][Row].color;
StringGrid1->Canvas->TextOut(Rect.Left+3,
Rect.Top+3,cellbuf[Col][Row].msg);
}
五、软件封面的实现
----现代软件设计的流行做法是,在程序运行完成初始化之前,先调用一幅画面做为封面,通常是1/4屏幕大小,显示一下软件的名称、作者、版本等信息。要用C++Builder实现这样的功能,方法很简单:①自定义一窗体类TSplashForm,将其设置成"透明窗口",即BorderIcons下的所有选项均置成false,BorderStyle=bsNone,FormStyle=fsStayOnTop,Position=poScreenCenter;②在TSplashForm窗体上放置一TPanel(相当于图形的镜框);③在TPanel上放置一TImage控件,调入所需要的图形;④对WinMain函数稍加修改,加入如下所示代码即可。需要指出的是,这段代码通过函数FindWindow,搜索内存中是否有窗口标题为"Demo"应用程序存在,若存在,则退出程序的运行。该功能可防止程序的再次运行。在某些场合这样设计是必须的。
WINAPIWinMain(HINSTANCE,HINSTANCE,LPSTR,int)
{
try
{
if(FindWindow(NULL,"Demo")!=0)
{
Application->MessageBox
("程序已经运行!","警告",MB_ICONSTOP);
return0;
}
TSplashForm*splash=newTSplashForm(Application);
splash->Show();
splash->Update();
Application->Initialize();
Application->CreateForm(__classid(TForm1),&Form1);
splash->Close();
deletesplash;
Application->Run();
}
catch(Exception&exception)
{
Application->ShowException(&exception);
}
return0;
}
六、如何永久清除DBF中的已被删除的记录
用table->Delete()删除的DBF记录,并没有真正从DBF数据库中被删除,而仅仅是做上了一个删除标记。如何实现类似dBase中的Pack命令的功能呢?请看下面的代码。 table->Close();
for(;;)
try
{
table->Exclusive=true;
table->Open();
break;
}
catch(...)
{
}
if(DbiPackTable(table->DBHandle,table->
Handle,NULL,szDBASE,true)!=DBIERR_NONE)
Application->MessageBox("不能删除记录",
"错误",
MB_ICONSTOP);
七、I/O端口读写的实现
细心的读者会发现,C++Builder不再支持如inportb()、outportb()一类I/O端口读写指令了。准确地说,在Windows环境下,BorlandC++仅支持16位应用程序的端口操作,对32位应用程序的端口操作不再支持,而C++Builder开发出来的程序是32位的。我个人以为,这是C++Builder设计者的败笔。因为PC机中,I/O地址空间与内存地址空间从来都是各自独立的。看看Delphi,不就通过Port数组实现了对I/O端口的访问了吗?搞不清楚为什么C++Builder就没有提供类似的机制?下面这几个函数是笔者从网上淘下来的,经过验证,在Windows95环境下,的确可实现对I/O端口的读写。读者可以借鉴使用。
voidoutportb(unsignedshort
intport,unsignedcharvalue)
{
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//moval,*(&value);
__emit__(0x8a,0x85,&value);
//outdx,al;
__emit__(0x66,0xee);
}
voidoutportw(unsignedshort
intport,unsignedshortintvalue)
{
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//movax,*(&value);
__emit__(0x66,0x8b,0x85,&value);
//outdx,ax;
__emit__(0xef);
}
unsignedcharinportb(unsignedshortintport)
{
unsignedcharvalue;
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//inal,dx;
__emit__(0x66,0xec);
//mov*(&value),al;
__emit__(0x88,0x85,&value);
returnvalue;
}
unsignedshortintinportw(unsignedshortintport)
{
unsignedshortintvalue;
//movedx,*(&port);
__emit__(0x8b,0x95,&port);
//inax,dx
__emit__(0xed);
//mov*(&value),ax
__emit__(0x66,0x89,0x85,&value);
returnvalue;
}
八、软件的分发
在Windows下开发的应用程序一般都比较庞大,程序的运行往往离不开一大堆不知名的系统DLL文件。为了生成能脱离C++Builder环境、独立运行的应用程序,读者须对编译器进行一定的设置。方法是:置Project/Option/Packages/Runwithruntimepackages为Disable,置Project/Option/Linker/UsesdynamicRTL为Disable,重新编译一遍程序,这样生成的EXE文件就可以脱离C++Builder环境运行了。但如果你的程序中应用了数据库,仅有上述的操作是不够的--因为,你还得安装BDE(BorlandDatabaseEngineer)。BDE的安装比较麻烦,读者最好是用C++Builder3.0附带的InstallShieldExpress来制作安装盘,把应用程序和BDE打包在一起。如果找不到,也可用Delphi3.0附带的InstallShieldExpress来制作。InstallShield的使用方法,限于篇幅,不再介绍。有条件的读者可上网查到有关资料。
本栏文章均来自于互联网,版权归原作者和各发布网站所有,本站收集这些文章仅供学习参考之用。任何人都不能将这些文章用于商业或者其他目的。( Pfan.cn )
【编程爱好者论坛】