testSys总结
Guozhen Sui(suigzh@nenu.edu.cn)
03-05-08
===========================================================================================
关键字:bcb c++ typedef 窗体间通信 TCanvas TMemo Lines ShowModal() 文件夹遍历
前言
---------------------------------------------------------------------------
这个系统是为心理系的一位朋友----一个很出色,优雅的女生----写的程序。它将用于测试
人的心理的什么东西。我对这个就不是很不清楚了。
在写这个系统之前,我已经很长时间不写程序了。一个是因为也没什么时间,一个是因为没
有什么值得写的。对于不同的开发平台,我曾接触了vb(被人们称之为垃圾的东西),vc,还
有就是bcb了。当然早期用了很长时间的tc,这是每一个人都应该接触的平台。总体的感觉是
borland的东西做的是很好的。尤其是它的帮助系统。有过开发经历的朋友都知道,一个好的
帮助系统对于开发将是一个多么重要的助手啊。对微软的东西,比如vc,你还要去花钱买
msdn。真是一个令人痛恨的暴发户。
在这里我想把我开发过程(这样的小程序也称为开发^;^)中所遇到的技术问题写出来。当然这
些问题如果你愿意花时间,你也可以找到答案。我只是希望如果你看到我写的这个东西,可
以节省一些时间来从事更加重要的事情。
如果你对前面的细节不感兴趣,你可以直接看第四部分,那是核心。
打住,进入正题。
1.系统要做什么
-----------------------------------------------------------------------------
我有朋友要求能做到在一个界面上显示汉字,测试者根据测试规则进行反聩,最终由程序对
反聩进行评价。具体如下:
汉字部分是正确的,真的汉字。另一部分是错误的,由我的朋友使用fireworks拚出来的。
所有的汉字保存在一个bmp文件中,大小是70*70象素。我猜想,对的汉字她只是在firewor-
ks中添加一个文字,并将之扩大;而错误的汉字,可能是建立两个图层,将组成的部分放在
不同的图层中,移动一下,使之看起来像一个汉字。她很聪明。
汉字要求隔一段时间显示(比如1s)。也就是说,汉字显示后,测试者给予反聩。从反聩后的
时刻开始计时,1s后,显示下一个汉字。直至所有的汉字都显示完毕。每个汉字不重复出现
而且是随机出现的。显然我们要使用时间控件了。
测试规则是:当测试者认为显示的字是对的,存在的汉字时,按f键或F键;如果显示的字是
错的,假的汉字的话,按j键或J键。之所以按这两个键,我想是因为比较顺手吧。
程序评价应给出如下内容:
a.测试者对每个汉字的反应时间,精度要求是ms(或者接近ms)
b.随机出现的汉字的顺序,每个汉字的属性(真或假)
c.测试者对每个汉字的答案,正确率
2.系统实现的思考
------------------------------------------------------------------------------
我首先觉得比较困难的是时间如何达到要求,bcb(c++ builder)实现的时间机制我并不是十
分清楚,只能找帮助文档或上网找资料了。
显示汉字应该比较容易,bcb中的大部分控件都有TCanvas类,这个类对windows图形操作做
了很好的封装。初步的想法是在主窗体上显示汉字,也就是显示bmp文件。
测试者通过键盘进行反聩,那么也就是要监视主窗体的键盘消息了。接到键盘消息后,检查
是不是正确按键,如果是进行相应的数据操作,显然要记录数据了。如果不是,不进行处理
。也就是说,汉字显示后到测试者按正确的键之间,时间控件是不计时的。这是如何做的呢
?
测试的汉字是有限的,要求我们建立一个机制,控制汉字的显示。所有汉字显示完毕后,显
示测试者的评价内容。显然,此外我们应该设计一个接口,使测试者能够选择继续测试或是
保存或是退出。
实际上,我的朋友给了我两套汉字。一部分用于测试者进行练习,一部分用于真正的测试。
在程序中应该给出选择测试还是练习的界面。
如何实现随机显示汉字也是一个问题。我想到的是问题可以简化为建立一个随机数的数组。
这个数组内的每个数是不同的。模型如下:
int aRandom[5];
aRandom[0] = 3;
aRandom[1] = 1;
aRandom[2] = 4;
aRandom[3] = 0;
aRandom[4] = 2;
我以前曾经用flash脚本和c语言实现了这个功能。参考一下以前的代码就可以了。保存自己
的作品是一个好习惯。
剩下的工作就比较好做了,比如计算正确率。计算机最不怕的就是计算。
3.我的初步系统设计
-----------------------------------------------------------------------------
程序接口
主窗体:背景为黑色,全屏幕显示。
规则说明窗体:程序进行开始时显示规则。按钮:练习,测试。
统计窗体:通过一个TMemo显示使用者的情况。最后一行给出统计数据。四个按钮:保存,
练习,测试,结束。
程序流程
程序运行,显示主窗体。显示规则说明窗体。规则对话框显示测试规则,当测试者选择测
试或练习后。开始执行相应模块。
测试与练习模块
显示字体,开始计时。当测试者给予反聩后,记录时间。记录正确性。
测试结束,显示统计窗体。根据测试者要求重来还是结束。
记录数据如何存储
采用一个结构体,存储字体文件名称,反应时间,正确与否,字体属性。
字模数据如何存储
系统开始时,载入所有的系统字模,并为之编号。
字模导入时间的确定
系统开始时导入还是在确定字体后导入,认为确定字体后导入会影响到字体显示速度。确
定在系统初始化时载入所有字模,当然这会使存储空间有一定的损失。经测试后决定吧。
如何确定一个字模是否以出现?
建立一个数组,包括编号和一个bool变量,表明字体是否出现。出现为真,否则为假。可以
参考以前的flash程序的相应部分。
4.实际问题即如何解决
----------------------------------------------------------------------------
4.1 数据的描述
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
我编写了一个头文件用来描述数据。如下:
1.#ifndef DATA
2.#define DATA
3.#define _DEGUG
4. // global data type and constants
5. #define NAMELENGTH 6 file://length of name array
6. #define MX (1.15740740658232e-8) // 1 ms
7. #define LENGTHPRACTICE 12
8. #define LENGTHTEST 127
9. // the record of a font;
10. typedef struct fontRecord
11. {
12. char fontName[NAMELENGTH];
13. TDateTime timeResponse; // save the response time
14. bool bCorrect; // the property of font determined on the
15. file://first letter of name
16. bool bAnswer; // the tester's answer
17. } FRECORD, *pFRECORD;
18. // end ...
19. // the font model data in system
20. typedef struct fontModel
21. {
22. char fontName[NAMELENGTH];
23. int iFontId; // the font id
24. bool bCorrect;
25. Graphics::TBitmap *bmpFont;
26. } FMODEL, *pFMODEL;
27. // end...
28. // used to judge the existence of a bitmap
29. typedef struct fontSequence
30. {
31. int iFontId; // the font id in the fraction of font name
32. bool bShowed; // wheather we have show it?
33. } FSEQ, *PFS;
34. // end...
35. #endif
当然,行号是在写这篇文档时加上去的。大部分注释是用半通不通的英文写的,看起来一定不
会很舒服。
现在我来做一下解释。
第3行,定义了_DEBUG,这样在程序调试的时候我可以省许多力气。比如通过如下结构:
#ifdef _DEBUG
ShowMessage("the current i = " + IntToStr(i));
#endif
而最终编译时只要把第3行拿掉就可以了。
第6行定义了MX,就是1ms了。如何实现,以后再说。
第7行以后定义了一些数据类型。比如第10行定义了一个字体的记录。它包含了字体的文件名,
本身属性,测试者的回答,测试者的反应时间。第20行定义了字模的数据:字体文件名,字体
属性,指向bmp文件的指针。
数据定义没什么可说的,关键在于typedef的用法。下面的语句是什么意思呢?
typedef PMODEL *arrP[] (*pFunction)(char *arrName, int iValue);
我在看了c trap这本书后才发现,原以为c学的还可以是多么的荒谬可笑啊。c语言还有很多东
西我没有学会啊。建议大家看看c trap这本书。书名我想我没有记错。
上面的语句可以视为两部分
typedef (PMODEL *arrP[]) ((*pFunction)(char *arrName, int iValue));
*1* *2*
也就是说1部分与2部分是相同的。1是一个数组,一个指针数组,一个指向PMODEL数据类型的指
针数组。2部分是一个函数,那么就是说2的值与1相同,即2的返回值是1的类型。就是说2是一
返回值为指向PMODEL数据类型的指针数组的函数。单看2部分,后面是入口参数,一个字符型指
针,另一个是整数。换句话说,2是一个参数为字符指针和整数的函数。*pFunction相当于函数
名,也就是说pFunction是一个指针,指向这个函数(其参数为字符指针和整数)。这样看来这一
句的作用是定义一个数据类型pFunction。pFunction是什么类型呢,pFunction是一个返回值为
指向PMODEL数据类型的指针数组的函数的指针,这个函数的参数字符型指针和一个整数。
****对于没看懂上面那行typedef的朋友,请仔细理解上面那段解释。对于那些理解的更好的朋
友,请写出你的理解与大家分享。
你可能以为这种定义没有用处,那你错了,错的很严重。请你参看c trap这本书。原书中给的
例子精采绝伦。都说中国人聪明,聪明的民族怎么会被别的民族欺侮,聪明的民族的国家怎么
会是一个发展中的国家。网上的朋友说的好:我们的民族缺少灵魂。
聊发议论,有辱清听。
4.2 窗体的背景颜色
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
这个问题很丢脸,是一个很菜的问题,但我确实找了老半天才完成它。
窗体属性中有Color一项,修改它的值为clBlack就可以了。
4.3 窗体的TCanvas并没有解决问题
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
定义了Graphics::TBitmap类型的指针后,你可以这样来显示一个bmp文件。
this->Canvas->Brush->Bitmap = aFModelT[i].bmpFont;
this->Canvas->FillRect(this->rcGlobal);
其中aFModelT[i].bmpFont是一个TBitmap的指针。在系统初始化时,装入了所有的字体。
aFModelP[i].bmpFont = new Graphics::TBitmap();
aFModelP[i].bmpFont->LoadFromFile(GetCurrentDir() +
".\\practice\\" + srPractice.Name);
这时,GetCurrentDir()用于获取当前目录,也就是你的程序所在的目录了。aFModelP[i]是一
FMODEL类型。TBitmap提供了读取文件中bmp图的方法----LoadFromFile(AnsiString *)。
用TCanvas显示bmp图,你会发现你给一个指定大小的TRect(0, 0, 70, 70)并不能达到好的效果
。图并没有整个显示出来,而是以一种另人难以忍受的形式出现。只能换一种方法了。
这里TCanvas提供了FillRect(TRect &rc)来填充指定的TRect。可能我没有设置正确,它没有给
我好的结果。
我使用了TImage类,它很好的解决了问题,而且汉字的擦除更回简单。只要将TImage的Visible
属性设为false就可以实现了。不过TImage装入bmp对我不太合适。TImage也提供了从文件导入
bmp的方法,TImage::LoadFromFile。但是我在系统初始化的时候,也就是主窗体的OnCreate时
,将所有的字体己装入内存了。因此,只能用其它方法。
下面的代码解决了问题:
this->imgMain->Visible = true;
Clipboard()->Assign(aFModelT[i].bmpFont);
imgMain->Picture->Assign(Clipboard());
imgMain是我加入的TImage对象的指针,首先将bmp图载入到剪切板中,然后从剪切板把bmp图分
配给imgMain。这样做的代价是须要在头文件中包含一个文件:#include <vcl\Clipbrd.hpp>
4.4 系统初始化时,字体是如何装载入内存的?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
所有的位图文件保存在两个文件夹中,fonts和practice。我将这两个文件夹放在程序所在文件
夹中。当主窗体初始化时,我调用了一个函数----InitializeFont()----把所有的字体装入内
存。事实上,实现这个功能也就是遍历整个文件夹,读取所有的文件。bcb(c++ builder)有非
常好的方法实现了这点。
0. TSearchRec srUnion;
1. if(FindFirst((GetCurrentDir() + ".\\fonts\\*.*"), faAnyFile, srUnion) == 0)
{
do
{
11. if(srUnion.Name != "." && srUnion.Name != "..")
{
ParseName(srUnion.Name, &aFRecordT[i]);
strcpy(aFModelT[i].fontName, aFRecordT[i].fontName);
aFModelT[i].bCorrect = aFRecordT[i].bCorrect;
aFModelT[i].iFontId = i + 1;
aFModelT[i].bmpFont = new Graphics::TBitmap();
2. aFModelT[i].bmpFont->LoadFromFile(GetCurrentDir() +
".\\fonts\\" + srUnion.Name);
aFSeqT[i].bShowed = false;
aFSeqT[i].iFontId = aFModelT[i].iFontId;
i++;
}
}while(FindNext(srUnion) == 0);
FindClose(srUnion);
}
第0行定义了TSearchRec变量,用来存储查找的内容。第1行,使用FindFirst方法把fonts文件
夹内的文件(实际上,是第一个符合条件的文件)装入srUnion;我通过一个例子发现FindFirst
把"."和".."也算作是符合条件的文件,显然二者是不存在的。需要判断一下,是否是这两个
文件。这是由第11行做的。第2行以前介绍过,也就是实现了把bmp文件装入内存。遍历需要执
行FindNext,以便把下一个符合条件的文件装入srUnion中。当遍历结束后,使用FindClose关
半变量srUnion。
4.5 显示规则窗体的实现
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
为程序添加一个窗体。当Timer第一次响应事件时,我载入了这个窗体。这样做的缺点是程序
运行后1s后,才显示这个窗体(规则)。
this->timeMain->Enabled = false;
try
{
1. this->frm = new TfrmIntr(this);
2. if (frm->ShowModal() == mrOk)
{
// the test choice
this->bTest = true;
this->bShowed = true;
}
else
{
// means to practice
this->bTest = false;
this->bShowed = true;
}
}
__finally
{
this->timeMain->Enabled = true;
3. delete this->frm;
}
第1行,frm是一个TForm的指针,为它new一个TfrmIntr实例,TfrmIntr是规则窗体的类,窗体
内通过一个TMemo控件来显示规则。第2行,调用窗体的ShowModal()方法,这个方法能够做到
测试者必须对这个窗体有所响应,如果不响应,你没有办法执行程序的其它部分。执行这个方
法,将返回窗体的ModalResult值。当我们在规则窗体内添加一个按钮,它的OnClick方法如下
:
void __fastcall TfrmIntr::btnTestClick(TObject *Sender)
{
ModalResult = mrOk;
}
点击这个按钮后,规则窗体就会关闭,并返回mrOk。这是我所知的窗体间通信的一个方法。第
3行,将这个窗体释放。在c++中,你需要注意new与delete。如果你申请了内存而不释放它,系
统就相当于丢失了一部分内存。这也是大多数风格不好的程序导致系统变慢的原因。通常的作
法是,new一个类的实例后,在使用后delete掉它。如果这个实例(或这段数据)是全局的,那你
就要很小心了,在程序退出时别忘了delete它。我是这样做的。
void __fastcall TfrmTestMain::FormClose(TObject *Sender,
TCloseAction &Action)
{
FreeMemory();
}
当程序退出时,我调用了自己写的FreeMemory()函数。FreeMemory()将我用过的全局的内存全
部释放掉了。
4.6 统计窗体没办法显示,另一种方案
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
在统计窗体中,我要显示统计数据,这些数据我装入一个数组中。这个数组是属于主窗体类的
public数据。现在的麻烦是,我如何调用这段数据。我想的办法是,在统计窗体类中,声名相
同的数据类型,在显示统计窗体之前调用一些函数,把数据传到这个窗体中。我确实这样做了
,但是显然没有成功。
this->frm = new TfrmStastic(this);
try
{
1. this->frm->InheritFont(this->aFModelP);
2. this->frm->InheritRecord(this->aFRecordP);
3. this->frm->SetBTest(fasle);
4. this->frm->PushData();
switch (this->frm->ShowModal())
{
case 1:
// means test section
this->bTest = true;
this->iShowNumber = 0;
this->ClearBool(this->aFSeqT, this->bTest);
break;
case 2:
// to practice
this->bTest = false;
this->iShowNumber = 0;
this->ClearBool(this->aFSeqP, this->bTest);
break;
case 3:
// to save
this->SaveScore();
break;
case 4:
// to Exit
this->Close();
break;
default:
// default
this->bTest = false;
this->iShowNumber = 0;
this->ClearBool(this->aFSeqP, this->bTest);
// clear bShowed in sequence
}
}
catch(...)
{
ShowMessage("Error in the show statistic module");
}
__finally file://*********
{
delete this->frm; file://*********
}
1,2,3,4这四句调用了一系列的函数,试图将数据传入这个窗体中,但遗憾的是这根本连编
译都通不过。我的结论是在窗体ShowModal()之前你没办法调用窗体的函数。因为编译给的
信息是:方法不是窗体类的成员。我曾经和一个计算机系的研究生写过一个java的程序,
没有写完。那时我也做了类似的工作,我在ShowModal()之后调用了窗体的函数,得到了结
果。也就是说,ShowModal()之后,窗体并没有从内存中flash出去。但是在ShowModal()之
前,你不能调用窗体的函数。而且我没有在bcb(c++ builder)中测试能否在ShowModal()之
后调用函数。有需要的朋友自己测试吧。请把结论告诉我。
我没有实现窗体之间的互相通信,真的很不幸。我不得不用另外的方法显示统计了。
我在主窗体加了一个TPanel,在TPanel里放入了TMemo。在初始化时,我让TPanel不可见。
而当要显示数据时,给TMemo赋值,然后使TPanel可见。原来打算放在统计窗体中的按钮也
是这样处理的。这样做极大的简化了我的工作。
4.7 如何在TMemo中显示统计数据。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
TMemo提供了Lines属性,利用这个属性我实现了显示统计数据。
this->memoShow->Lines->Clear();
AnsiString strShow = "";
strShow = "统计说明:";
this->memoShow->Lines->Add(strShow);
strShow = "==========================================================";
this->memoShow->Lines->Add(strShow);
strShow = "1.字体文件名按字体出现顺序显示";
this->memoShow->Lines->Add(strShow);
strShow = "2.字体属性表示该字体为真字还是假字";
this->memoShow->Lines->Add(strShow);
strShow = "3.回答表示测试者的答案是否正确";
this->memoShow->Lines->Add(strShow);
strShow = "4.时间表示测试者反应时间";
this->memoShow->Lines->Add(strShow);
strShow = "==========================================================";
this->memoShow->Lines->Add(strShow);
如上,我通过调用Lines属性的Add()方法实现了统计说明头部的添加。具体数据的添加
只是在一个循环中进行的。这样做很容易。
4.8 正确率的计算
使用一个整型变量来存储正确的答案的个数,用它除以字模总数就可以了。要求保留两位
小数,这有点不太好办。我是这样实现的。
strShow = "正确率:\t\t" +
FloatToStr((((double)((10000 * iRate)/LENGTHPRACTICE))/100)) +
"%";
比如说正确的个数是1,总数是2。(考虑最简单的例子并把它推广,这是一个好的方法)我
们要的结果应是50.00%。这要怎么做呢。首先用10000乘以1,得10000。然后除以总数。由于
是整数与敕数的除法,我们得到是整数5000。现在将5000转化为浮点数,(double)(5000)。现在
得到了浮点数5000如果在除以100,得到正确的结果,50.00%。
4.9 程序完成后,需要加入哪些库文件?+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
以前在网上看到一个高手写的文章,对vb批评了许多。vb程序运行时需要一个库的支持,这个大小
约为2M。而bcb的程序不仅小,面且不需要运行库的支持。
我正是看了这篇文章才学习bcb,但现在看来那位高手好像犯了错误。从我用bcb写的第一个程序起
,我就发现bcb的程序也需要动态库的支持。你可以读一下bcb的Deploy.txt(在bcb的安装目录下)。
C++Builder 5 applications do not require a runtime interpreter
DLL. All you have to provide is your .EXE file and any DLLs
or BPLs that it calls. For simple applications you can
distribute a standalone .EXE file. If you have used runtime
packages with your application, be sure to include all
required BPLs with the distribution. If you build using the dynamic
Run-Time Library, you will also have to distribute one or more of the
Run-Time Library DLLs CC3250.DLL or CC3250MT.DLL and the memory
manager Dlls BORLNDMM.DLL, BCBMM.DLL or DELPHIMM.DLL. Applications
that use MFC may also need BFC42.DLL or BFC42D.DLL.
这段话写的很清楚,你要提供你的程序与和任何它调用的dll和bpl文件。显然,bcb程序一定调用到
这个文件,vcl50.bpl。(我是指你在编译时使用borland c选项,如果你用其它的,我就想就不会了
)此外,你也需要调用其它内容。在我的程序中,由于使用了剪切板,我不得不加上borlndmm.dll。
最终我的程序文件如下:
2000-05-07 18:31 41,472 psychology.exe
2000-05-08 09:39 <DIR> practice
2000-01-31 05:00 25,600 borlndmm.dll
2000-01-31 05:00 1,496,064 cc3250mt.dll
2000-01-24 05:01 2,023,424 vcl50.bpl
2000-05-08 09:39 <DIR> fonts
这里vcl50.bpl这个库,必须加入到你的程序中。
因此,如果你想写运行于windows平台下的程序,如果你想写的特别小,一定要使用windows api编
程。以前曾经看到一个高手写的Web服务器程序只有24k,真是佩服死了。有关windows api编程的
内容请大家到网易上找找,曾有个朋友说网易上有很好的教程。
5. 一些问题
--------------------------------------------------------------------------
a. 窗体间是如何通信的?
b. 在bcb中,如何建立全局数据。比如建立一个数组,要求所有的窗体都可以访问到它?
c. 如何判断你的程序需要调用哪些库?
d. 会有人给我答案吗?
感谢:
---------------------------------------------------------------------------
我想首先谢谢图书馆的陶老师,我占用了她的机器写程序。谢谢你的支持。
感谢计算机的研究生赵老师,谢谢你提供了java的源代码。
感谢我的导师张老师,她对我是那么宽容。使我有胆量不去做本科论文试验而跑来定这篇
文字。
感谢我的父母。他们花费金钱让我念大学而我却没有什么成绩。
后记:
---------------------------------------------------------------------------
上面我把我在写这个程序时的心得写了出来,希望有的朋友能够用的着。
时间很仓促,我来不及校稿,只能请朋友们原谅了。如果你能从中学到什么,这是我最大的
快乐。
作为一名化学系大四的学生,我不得不马上回到实验室去做我的论文试验了。我对化学没
有兴趣。一个人要做的事不是自己的兴趣,该是一件多么痛苦的事啊。
谢谢你读到这。