开发一个向导程序
通常向导程序有这样一些特征:
1. 都是对话框,从一个对话框跳到另一个对话框,还可以根据选择再跳回来,是一种以路径为导向的。
2. 向导的下部包括控制路径的几个按钮:上一步、下一步、取消,等等。
3. 通常向导中会包含一道长时间的操作,在这个操作之前需要配置该操作的环境。
最典型的向导就是InstallShield安装程序或者Microsoft Installer程序。
有时候我们需要做一些数据迁移工作,将数据从一个平台迁移到另外一个平台。这时候我们可以做一个向导程序来完成。最简单的思路或者说设计就是将每个对话框做成一个独立的Form,然后Application会根据用户的选择在多个对话框之间流转。这种设计存在一定的缺陷:每个对话框中都必须包含流转控制的代码,也必须包含整个页面,包括一些相同的页面元素。对于很多人来说,这是不能接受的。所以,下面的设计比较符合他们的思路。
定义一个Form,这个Form上包含了所有的公共元素:![](https://www.cnblogs.com/images/cnblogs_com/barton131420/image001.jpg)
其中包括一个预留的区域,用于加载每个帧(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中则更简单,在项目中直接通过上下文菜单选择添加→添加继承的用户控件)。