通常向导程序有这样一些特征:
1.
都是对话框,从一个对话框跳到另一个对话框,还可以根据选择再跳回来,是一种以路径为导向的。
2.
向导的下部包括控制路径的几个按钮:上一步、下一步、取消,等等。
3.
通常向导中会包含一道长时间的操作,在这个操作之前需要配置该操作的环境。
最典型的向导就是
InstallShield
安装程序或者
Microsoft Installer
程序。
有时候我们需要做一些数据迁移工作,将数据从一个平台迁移到另外一个平台。这时候我们可以做一个向导程序来完成。最简单的思路或者说设计就是将每个对话框做成一个独立的
Form
,然后
Application
会根据用户的选择在多个对话框之间流转。这种设计存在一定的缺陷:每个对话框中都必须包含流转控制的代码,也必须包含整个页面,包括一些相同的页面元素。对于很多人来说,这是不能接受的。所以,下面的设计比较符合他们的思路。
定义一个
Form
,这个
Form
上包含了所有的公共元素:
其中包括一个预留的区域,用于加载每个帧
(TFrame
,在
.net
中是
UserControl)
。现在不考虑帧的事情,先想想在这个
Form
中有哪些内容是由帧来控制的。
1.
标题。即标题栏中的
RzLabel1
和
RzLabel2
两个部分。第一个标签用于描述该页的内容,第二个标签为该页行为的具体描述,还可以描述一些具体操作的指导。
2.
按钮状态。即什么时候“上一步”有效、什么时候“下一步”有效。如果向导程序完成,“取消”
按钮应该变成“完成”按钮。
3.
按钮行为。即按下不同的按钮会产生什么行为。
4.
如果可能的话,对话框左侧的图片和标题栏右侧的图片也需要由每个帧来提供。
按照这个目标,我们来设计一下这个帧应该提供什么功能。
1.
一个
Caption
属性和一个
Description
属性,字符串类型,分别用来填充
Form
上的
RzLabel1
和
RzLabel2
。
2.
一个
PreviousPage
属性和一个
NextPage
属性,先不考虑他们什么类型。他们的功能就是在按下“上一步”和“下一步”按钮时,执行什么动作。
3.
一个
IsComplete
属性,布尔类型,说明此页是否为结束页。如果是结束页则修改“取消”按钮为“完成”按钮。
4.
一个
CanAbort
属性,布尔类型,说明该页是否可以被用户终止。如果返回为
False
时则
Disabled
那个“取消”按钮。
5.
一个
ShowPage
方法,没有任何参数,用于被激活时由
Form
主动调用。
6.
一个
Abort
方法,没有任何参数,用于在长时间处理中,用户按下“取消”按钮时页面的响应。
7.
一个
OnPageChange
事件,参数就是帧,也就是我们设计的目标类型。用于页面内部调用
Form
的功能,例如,但页面上某个编辑框内容为空时,将“上一步”或“下一步”按钮置为
Disabled
,当这个编辑框内容不为空时再将按钮置为
Enabled
。本质上,就是一个回调。
现在来研究一下,
PreviousPage
和
NextPage
属性采用什么类型。通常我们这样的设计要求达到这样一个目标:每个帧之间、帧与
Form
之间尽量不要耦合,就是不必知道对方是如何实现的。为了这个目标,我们需要花点时间研究这个机制。
首先是数据机制。向导的目标就是用户对数据的选择。也就是说,在某些页面
(
例如执行导入的页面
)
上必须知道另外的页面所设置的数据。这些数据可以使用一个专门的单元来处理,这个单元对每个页面都是开放的。往往这种耦合是必要的。在这个单元中设置一些全局变量,让相关的页面来访问。
其次是页面机制。在实施前,往往需要画一下流转图,在什么条件下由一个页面转到另外一个页面。通常都是这种形式:
u
开始页
(StartPage)
u
设置源
(SourcePage)
u
设置目标
(DestinationPage)
u
确认
(ReadyPage)
u
执行导入
(ImportingPage)
有了这张表,现在简单了。我们为每个页面赋一个别名
(
例如
StartPage)
,然后设计一个专门的别名管理器,每个页面都自己向这个页面管理器注册自己。然后所有的页面都可以通过这个别名来获得页面的实例。所以,最后确定,
PreviousPage
和
NextPage
属性采用字符串类型。那么,这个别名管理器用什么来实现?最好的选择就是采用一个
TStrings
了
(
如果在
.net
中可以使用
System.Collections.Hashtable)
。每个项就是一个别名对应一个页面的实例。不要考虑用页面的类名来作为页面的别名,这样你就不可以通过不同的页面名称来实例化相同的页面类型了。考虑一下这个别名管理器谁来负责其生命周期?很简单。在这个页面框架所在的单元的初始化部分
(initialization)
创建,然后在清理部分
(finalization)
释放。同样,在每个页面所在单元的初始化部分
(initialization)
创建并向别名管理器注册相关的实例;而在清理部分
(finalization)
注销这些实例。如果是.net就不必管这些实例什么时候被释放了。
我们将这个页面的基类命名为
TPage(
在
.net
中为
Page)
,创建的方式是新建一个
TFrame(
在
.net
中为
UserControl)
,什么内容也没有。然后加上上面的设计。剩下的事情就是一个个地建立这些页面。你可以直接
New
→
Other
→您的项目名→
TPage(
如果在
.net
中则更简单,在项目中直接通过上下文菜单选择添加→添加继承的用户控件
)
。