C++Builder3.0使用经验谈
钟伟
-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的使用方法，限于篇幅，不再介绍。有条件的读者可上网查到有关资料。