更多感受-Hello VCL (二)
续 << Hello VCL (一)
5.4 “提醒项”编辑框
当用户点击“增加”按钮时,我们希望弹出一个小窗体,让用户输入新提醒项的内容:标题和提醒时间。而当用户点击“修改”按钮时,我们同样希望弹出一个小窗体,让用户修改原有
提醒项的内容。本次设计中,这两个窗体将共用一个窗体。现在就让我们来添加、设计该窗体。
在Turbo C++
Explores中,选择主菜单:File->New->Form。创建出一个新的窗体。创建新窗体时,自动也创建出新的单元文件,在我这里,该单元文件被命名为“Unit3.cpp”,当然,也会有一个同名的头文件:“Unit3.h”,而窗体,则被命名为“Form3”(这里的尾缀3,可能仅仅是为了和单元文件Unit3保持一致?我们是有Form1,但没有Form2)。
在本窗体中,我们又将认为一个新类型的控件:TDateTimePicker。它同样位于“Win32”的控件页上。如下图所示,我们需要添两个TDateTimePicker控件,并通过设置不同的属性值,使左边的一个用于选择日期,右边的一个用于选择时间。
其余的控件,分别是一个Label、一个Edit、两个Button。

(提醒项编辑窗体)
Step1:Form3属性设置
毫无疑问,您应该首先设置该窗体的字体/Font属性,使其为“宋体、五号、GB2312”等等。然后修改其标题等等,详情如下:
| 属性 |
值 |
说明 |
| Caption |
提醒项 |
|
| Font |
宋体、CHINESE_GB2312、五号 |
|
| Position |
poDesktopCenter |
|
| BorderStyle |
bsDialog |
对话框 |
| Scaled |
False |
|
注意和主窗口Form1不同,BorderStyle/边框风格,被设置为对话框。
Step2:日期/时间属性
界面上左边的日期时间控件,请确保其“Kind”属性为dtkDate(默认值)。
界面上右边的日期时间控件,请将其“Kind”属性,修改为dtkTime。
Step3:确认和取消按钮
“确认”按钮:
| 属性 |
值 |
说明 |
| ModalResult |
mrOk |
用户按该按钮时,表示要确认本次输入有效 |
| Default |
True |
当用户按回车键时,相当于点击“确认”按钮。 |
“取消”按钮:
| 属性 |
值 |
说明 |
| ModalResult |
mrCancel |
用户按该按钮时,表示要取消本次输入 |
| Cancel |
True |
当用户按Esc键时,相当于点击“取消”按钮。 |
Step4:确保该窗口被自动创建
按Ctrl + Shift +
F11键,或通过菜单“Project”->“Options”进入工程设置对话框。在左边选项树中,选中“Forms”,然后确保“Form3”也在“Auto-create
forms”的列表框中。(凡是列在该框中的窗体,都将在程序运行时,被自动创建出来)。

(确保Form3也在自动创建的窗口列表中)
如果是C++ Builder6的版本,则进行工程设置框时,选中“Forms”页,后续操作同上。
Step5:头文件
切换到Unit3.h文件:
//Unit3.h://---------------------------------------------------------------------------
#ifndef Unit3H
#define Unit3H
//---------------------------------------------------------------------------
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ComCtrls.hpp>
//---------------------------------------------------------------------------
class AlertItem; //本行需添加
class TForm3 : public TForm
{
__published: // IDE-managed Components
//(此处原有代码略)
private: // User declarations
public: // User declarations
__fastcall TForm3(TComponent* Owner);
//增加以下两行,申明两个成员函数:
void GetAlert(AlertItem * item) const;
void SetAlert(AlertItem const * item);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm3 *Form3;
//---------------------------------------------------------------------------
#endif
|
(Unit3.h)
其中GetAlert函数,将用于获得用户在界面上输入的提醒内容;而SetAlert函数正好相反,它根据一个已有的提醒项,显示到界面上。具体实现请见下一步。
Step6:源文件
切换到Unit3.cpp:
//Unit3.cpp://---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit3.h"
#include "Unit2.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm3 *Form3;
//---------------------------------------------------------------------------
__fastcall TForm3::TForm3(TComponent* Owner)
: TForm(Owner)
{
this->Edit1->Clear();
this->DateTimePicker1->Date = TDateTime::CurrentDate();
this->DateTimePicker2->Time = TDateTime::CurrentTime();
}
//---------------------------------------------------------------------------
void TForm3::GetAlert(AlertItem * item) const
{
item->caption = Edit1->Text;
item->time = (int)DateTimePicker1->Date.Val
+ DateTimePicker2->Time.Val - (int)DateTimePicker2->Time.Val;
}
//---------------------------------------------------------------------------
void TForm3::SetAlert(AlertItem const * item)
{
if (item != 0)
{
Edit1->Text = item->caption;
DateTimePicker1->Date = (int)(item->time.Val);
DateTimePicker2->Time = item->time.Val - (int)item->time.Val;
}
}
//---------------------------------------------------------------------------
|
(Unit3.cpp)
5.5 完成主界面交互
接下来,让我们回到Form1主界面,完成4个按钮的功能。
由于主界面的交互代码,必须用到有关“提醒项”数据定义,及窗体Form3,所以我们先在Unit1.cpp前面,加入对Unit2.h和Unit3.h的包含语句(部分代码为原有,请注意),另外,也加入一个全局数据alertList,用于保存提醒项的列表。
切换到Unit1.cpp:
//Unit1.cpp://---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#include "Unit2.h"
#include "Unit3.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
AlertList alertList; //用于保存所有提醒项
//---------------------------------------------------------------------------
|
(Unit1.cpp中,加入对另外两个头文件的包含,并定义alertList)
Step1:增加
双击“增加”按钮,出现该按钮的点击事件响应函数,填写代码:
//Unit1.cpp://---------------------------------------------------------------------------
void __fastcall TForm1::ButtonAddClick(TObject *Sender)
{
if (Form3->ShowModal() == mrOk)
{
AlertItem alertItem;
Form3->GetAlert(&alertItem);
alertList.AddItem(alertItem.caption, alertItem.time);
TListItem* item = ListView1->Items->Add();
item->Caption = AlertItem::GetStatusCaption(AlertItem::waitting);
item->StateIndex = 0;
item->SubItems->Add(alertItem.caption);
item->SubItems->Add(alertItem.time.DateTimeString());
}
}
//---------------------------------------------------------------------------
|
(按钮“增加”的点击事件代码)
Form3->ShowModule(),会弹出Form3窗口(5.4小节所设计的“提醒项”编辑窗口),当用户输入并按确认按钮后,ShowModule()函数会返回值为mrOk。然后进入添加
提醒项的具体代码,包括:
从Form3中得到用户刚刚输入的提醒内容:Form3->GetAlert(...); 将新提醒项加入提醒链表数据:alertList.AddItem(...);
在界面中显示。 Step2: 删除
双击“删除”按钮,出现该按钮的点击事件响应函数,填写代码。
//Unit1.cpp://---------------------------------------------------------------------------
void __fastcall TForm1::ButtonRemoveClick(TObject *Sender)
{
int selectedIndex = this->ListView1->ItemIndex;
if (selectedIndex != -1
&& IDOK == ::MessageBox(Handle, "确认要删除?", "删除提示"
, MB_OKCANCEL | MB_ICONQUESTION))
{
this->ListView1->Items->Delete(selectedIndex);
alertList.RemoveItem(selectedIndex);
}
}
//---------------------------------------------------------------------------
|
(按钮“删除”的点击事件代码)
删除过程不需要用到Form3,但我们会弹出一个消息框,提问用户是否真的要删除,它通过MessageBox这一API函数来实现。确认以后,先从界面上(ListView1)删除该项,然后从内存数据alertList中删除。
Step3: 修改
双击“修改”按钮,出现该按钮的点击事件响应函数,填写代码。
//Unit1.cpp:
//---------------------------------------------------------------------------
void __fastcall TForm1::ButtonEditClick(TObject *Sender)
{
int selectedIndex = this->ListView1->ItemIndex;
if (selectedIndex != -1)
{
AlertItem* alertItem = alertList.GetItem(selectedIndex);
Form3->SetAlert(alertItem);
if (Form3->ShowModal() == mrOk)
{
Form3->GetAlert(alertItem);
alertItem->status
= (alertItem->time >= TDateTime::CurrentDateTime()) ?
AlertItem::waitting : AlertItem::past;
TListItem* listItem
= ListView1->Items->Item[selectedIndex];
listItem->Caption
= AlertItem::GetStatusCaption(alertItem->status);
listItem->StateIndex = alertItem->status;
listItem->SubItems->Strings[0] = alertItem->caption;
listItem->SubItems->Strings[1]
= alertItem->time.DateTimeString();
}
}
}
//---------------------------------------------------------------------------
|
(按钮“修改”的点击事件代码) 首先从内存数据中,得到用户当前选中要修改的“提醒项”数据:
AlertItem* alertItem = alertList.GetItem(selectedIndex);
然后将它显示到Form3上:Form3->SetAlert(alertItem);
弹出Form3窗体,等待用户修改,并按确认按钮,进入具体的修改代码:
得到Form3的修改内容:Form3->GetAlert(...);
由于用户可能修改了提醒时间,所以需要重新判断该提醒项是否过期?
alertItem->status = ......
在主界面上,显示其它修改内容。
Step3:关于
双击“关于”按钮,出现该按钮的点击事件响应函数,填写代码。
//Unit1.cpp://---------------------------------------------------------------------------
void __fastcall TForm1::ButtonAboutClick(TObject *Sender)
{
::MessageBox(Handle, "定时提醒小助手 V1.0 \r\n www.d2school.com\r\n第2学堂"
, "关于...", MB_ICONINFORMATION | MB_OK);
}
//---------------------------------------------------------------------------
|
(按钮“关于”的点击事件代码)
简单地通过调用Windows的API函数MessageBox,来显示关于本软件的版本等信息。您尽可以在其中加入您的尊姓大名。其中\r\n用于表示回车换行。
现在,我们完成了主要界面交互代码,不过,该软件的核心功能:定时提醒,我们还一点没碰呢。 5.6 定时器
C++ Builder提供一个“定时器/TTimer”控件,它可以每隔一段时间,就触发一个事件。这样,我们就可以在该事件中,写代码检查每一个提醒项中的时间是不是到点了?到点的话,就弹出一个消息框,上面写着提醒的内容。
在主界面上添加一个TTimer控件(在System控件页上)。
检查Timer1的“Interval”属性是否为1000(1000毫秒,即1秒)。该属性控制定时器触发事件的间隔时长。设置为1秒(默认值),也就意味着我们的定时精确度差不多为1秒,这完全达到我们的要求了,就算是搞个定时炸弹,能精确到1秒也行了吧。
双击Timer1,写定时事件代码:
//Unit1.cpp:void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
Timer1->Enabled = false;
TDateTime current = TDateTime::CurrentDateTime();
for (int i = 0; i < alertList.GetCount(); ++i)
{
AlertItem* item = alertList.GetItem(i);
if (item != 0 && item->status != AlertItem::past
&& item->time <= current)
{
ListView1->Items->Item[i]->StateIndex
= AlertItem::alertting;
ListView1->Update();
MessageBoxEx(Handle, item->caption.c_str()
, "有事提醒您!"
, MB_ICONINFORMATION | MB_OK | MB_TOPMOST
, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT));
item->status = AlertItem::past;
ListView1->Items->Item[i]->StateIndex = AlertItem::past;
ListView1->Items->Item[i]->Caption
= AlertItem::GetStatusCaption(AlertItem::past);
ListView1->Update();
}
}
Timer1->Enabled = true;
}
//---------------------------------------------------------------------------
|
(定时事件代码)
for是一个循环语句,这里通过它来将alertList中的所有提醒项检查一圈,如何检查呢?
首先,如果该提醒项的状态已经是“过时”状态——也就是它多半是已经提醒过了——那就不用再检查;否则就比较一下它的提醒时间是不是小于当前时间(时间值越小,就越靠前,比如2001年比2007年靠前)。所以在判断条件,有一项是:
item->time <= current
其中item->time是某提醒项预定的提醒时间,而current则是前面代码得到的当前时间,那么假如:item->time是2006年10月24日1点30分0秒,而current已经是2006年10月24日1点30分1秒,就说明这一项得提醒了!
最后,您可能注意到了,我们用到了MessageBoxEx这个函数,这个带Ex的消息框版本提供一种增加功能:即我们可以控制它在显示时,总是显示在屏幕的最前面。
保存并编译工程,运行,测试一下各个按钮,通过“增加”按钮添加一项提醒项,设置一个合适的提醒时间(比如你要是设置一个1秒或1年以后的时间,我个人认为就相当的不合适),进行测试。

(定时提醒软件运行画面)
事实上,设置好要提醒的内容后,就可以将“小助手”窗口最小化,然后专心在电脑上忙您的其它工作了,某项提醒时间一到,“小助手”就会弹出一个消息,并且如前所言,这个提醒消息框会显示到最前面,这样不管可以防止您没有注意到它。
5.7 保存功能
假设明天有一球赛,那我今天设置上提醒项,可是只要退出这个程序,这个提醒项就丢光光了……让我们来为它加入保存功能吧。 TForm有两个事件:OnCreate和OnDestroy,分别发生窗体的界面被创建后和销毁前。我们希望在窗体被创建之后,从文件读入先前保存的提醒项;而在窗体即将被销毁前,保存当前所有提醒项。
Step1:OnCreate
双击主窗体的空白处,代码窗口自动进入窗体的OnCreate事件函数:
//Unit1.cpp:void __fastcall TForm1::FormCreate(TObject *Sender)
{
AnsiString fileName = ExtractFilePath(Application->ExeName) + "alertlist.dat";
alertList.Load(fileName);
for (int i = 0; i < alertList.GetCount(); ++i)
{
AlertItem* alertItem = alertList.GetItem(i);
TListItem* item = ListView1->Items->Add();
item->Caption = AlertItem::GetStatusCaption(alertItem->status);
item->StateIndex = alertItem->status;
item->SubItems->Add(alertItem->caption);
item->SubItems->Add(alertItem->time.DateTimeString());
}
}
//---------------------------------------------------------------------------
|
(创建窗口界面之后,自动读入之前保存的内容)
Step2: OnDestroy
OnDestroy不是Form的默认属性,所以只能通过“对象检视器”进行设置:

(双击 OnDestroy右边的空白) 双击 OnDestroy右边的空白,代码编辑窗口出现FormDestroy函数体,编辑代码:
//Unit1.cpp://---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
AnsiString fileName = ExtractFilePath(Application->ExeName)
+ "alertlist.dat";
alertList.Save(fileName);
}
//---------------------------------------------------------------------------
|
(销毁窗口界面之前,自动保存内容)
在OnCreate和OnDestroy事件代码,都用到了一个由VCL提供的函数调用:ExtractFilePath(Application->ExeName)。其参数“Application->ExeName”可以运行期间,得到程序可执行文件的完整路径及文件名称。而ExtractFilePath函数本身,则剥除其中的文件名,最终得到可执行文件所在路径。我们利用这一结果,来将提醒项目的文件保存和程序相同的路径下,文件名固定为“alertlist.dat”。
保存并编译程序。现在,我们可以连续安排多天的提醒项了,今天下午提交工作报告、明天晚上看球赛,后天上午和女友有约……统统搞定!所谓“好记忆不如烂笔头”,就让我们用这个小作品,作为我们当上预备程序员的一个礼物吧,希望它能帮上你点什么。
5.8 XP界面风格
可能您发现的,我的程序运行截图中,显示的窗口或按钮等控件,支持XP的界面风格。对于Turbo C++
版本,这是很容易的事,只须在“Win32”控件页上找到“TXPManifest”控件,扔一个在主窗体上即可。
作业:
- 将课程中有关VCL解释的那段英文,翻译成中文。
- 结合《Hello OO》课程中有关构造函数和析构函数的内容,将本课程中OnCreate中的内容,改为在TForm1的构造函数中实现,而OnDestroy中的内容,改为在TForm1的析构函数中实现。以下是提示:
a)TForm1的构造函数已经默认存在。在Unit1.h中,已经存在:
public: // User declarations
__fastcall TForm1(TComponent* Owner);
而Unit1.cpp中,您可以找到对应的函数实现。
b)析构函数您需要在Unit1.h中先添加声明,请添加在前述构造函数之后:
public: // User declarations
__fastcall TForm1(TComponent* Owner);
__fastcall ~TForm1();
其中__fastcall的修饰是必须的。
然后在Unit1.cpp中,同样在已存在构造函数之后,添加析构函数的实现:
__fastcall TForm1::~TForm1()
{
//......
}
-
如果您是通过“复制,粘贴”大法完成本课程中的代码,那么提醒您,这种小聪明可以用来帮助迅速对课程有一个完整的认识……但在完成之后,您还必须完完整整地动手完成一次课程所有代码的输入,编译,调试——你会在这一过程中学习,领悟到很多,否则在以后的学习中,您很可能成为又一个永远离不开老师的学生。
-
检查代码中,有关AlertList类实现的Load和Save函数,最后一行的delete fs;是否遗漏?请查阅以前的课程《Hello
Database》中,有关auto_ptr的使用方法,尝试将上述两处的fs套用上auto_ptr(以实现不必通过显式地写delete
fs来删除fs)。
<< Hello VCL (一) |