最近因项目需要,我自己设计开发了一个基于Windows Forms的向导开发框架,目前我已经将其开源,并发布了一个NuGet安装包。比较囧的一件事是,当我发布了NuGet安装包以后,发现原来已经有一个.NET的向导开发框架了,它叫Microsoft Visual Studio 2013 Wizard Framework。我并没有对其进行深入研究,单从名称上看,该框架是否只能在Visual Studio 2013下使用?上网搜索过,也没发现微软有比较详细的官方资料介绍这个框架。不过无论如何,我还是在此向大家介绍一下我自己开发的这个向导框架,也算是让大家了解一下我的设计思路,以及使大家能够方便地从该框架获益,快捷地在自己的项目中也用上这个向导框架。
有图有真相
话不多说,请先看效果图。为了演示这个框架,我依赖它开发了一个模拟软件安装过程的向导程序。用过类似Install Shield的安装程序的用户,应该对下面的这些对话框比较熟悉吧:
怎么样?看上去还算专业吧?它就是用Wizard Framework开发的。1.0.0版本支持以下功能:
- 向导对话框可以定制,比如可以自定义对话框的尺寸、Icon、是否支持在线帮助等等
- 由Windows Forms设计器支持的向导页面设计,开发人员可以像开发一个用户控件一样,直接在Visual Studio中使用拖拽的方式,设计每个页面的界面
- 每个页面都可以通过CanGoPreviousPage、CanGoNextPage、CanGoFinishPage以及CanGoCancel四个属性,直接设置向导对话框中“上一步”、“下一步”、“完成”和“取消”按钮的状态
- 每个页面都可以读取其它任何页面所保存的向导模型(WizardModel),通过向导模型获取各个页面的设置参数(比如上面“安装信息汇总”页面中就读取了“软件功能选择”页面的数据并显示出来)
- 每个页面都可以直接设定其它页面是否在上一步或者下一步可见,比如,在有些情况下,当当前页的某个参数被设置后,我们希望在点“下一步”的时候,能够跳过下一页,而直接进入下下页
- 每个页面都可以设置自己的Logo
- 对C# 5.0中async/await的支持,使得面向向导的异步开发模型变得异常简单
- 支持中文和英文
那么,我该如何获得源代码或者开发包呢?
源代码与NuGet安装包
你可以直接访问Wizard Framework的主页:https://github.com/daxnet/wizard-framework。如果你装有Git客户端的话,可以将本项目克隆到本地:
git clone https://github.com/daxnet/wizard-framework
等克隆结束后,直接在Visual Studio 2013中打开WizardFramework.sln即可(目前Wizard Framework基于.NET Framework 4.5.1开发,所以建议还是用Visual Studio 2013打开)。此时,你可以看到该解决方案包含两个项目:
WizardFramework项目就是该框架的源代码,而InstallerSample是一个Windows Forms应用程序,它使用了WizardFramework开发了一个模拟软件安装过程的向导界面,也就是上面你所看到的界面效果了。你可以修改Program.cs中Main函数的第一条语句,将本地化信息设置为zh-CN或者en-US,来体验该模拟程序在不同区域语言下的界面效果。
如果你希望在自己的Windows Forms项目中使用Wizard Framework,你可以在项目上单击鼠标右键,选择Manage NuGet Packages菜单项,在弹出的对话框中搜索WizardFramework关键字即可:
选中之后,单击“安装”按钮,即可将本向导开发框架添加到你的项目中(注意:建议应用程序是基于.NET Framework 4.5.1开发的)。
使用方法
通过NuGet Package Manager添加了Wizard Framework的引用之后,就可以开始开发向导应用了。基本上可以分三个步骤:开发向导页、开发向导对话框,以及将向导页添加到向导对话框。
开发向导页
要开发一个向导页,只需在Visual Studio的Windows Forms项目上,添加一个用户控件(UserControl),然后使其继承于WizardFramework.WizardPage类即可。此时,Visual Studio编辑器会提示构造函数错误,因为WizardPage类型没有可访问的默认构造函数,这就需要通过自定义的向导页类的构造函数向基类传入参数。以下是WizardPage构造函数的重载,以及各重载构造函数的参数描述。
- WizardPage(string title, string description, Wizard wizard, IWizardModel model = null)
- title:用于显示在每个向导页上方黑体标题部分的标题信息
- description:用于显示在每个向导页上方的向导页描述信息
- wizard:当前向导页所在的向导对象,一般通过向导页的构造函数参数传入
- model:当前向导页所使用的数据对象模型
- WizardPage(string title, string description, Wizard wizard, WizardPageType type)
- title:用于显示在每个向导页上方黑体标题部分的标题信息
- description:用于显示在每个向导页上方的向导页描述信息
- wizard:当前向导页所在的向导对象,一般通过向导页的构造函数参数传入
- type:当前向导页的类型。分为两种类型:Standard和Expanded。Standard类型的意思是,当显示该向导页时,会在向导对话框的上方显示title和description信息;而对于Expanded类型,则这部分信息不会显示出来,整个页面的设计完全由开发人员自己控制。显然,在上面的示例中,2、3、4、5页都是属于Standard类型的向导页,而1和6页则属于Expanded类型
- WizardPage(string title, string description, Wizard wizard, IWizardModel model, WizardPageType type)
- title:用于显示在每个向导页上方黑体标题部分的标题信息
- description:用于显示在每个向导页上方的向导页描述信息
- wizard:当前向导页所在的向导对象,一般通过向导页的构造函数参数传入
- model:当前向导页所使用的数据对象模型
- type:当前向导页的类型。分为两种类型:Standard和Expanded。Standard类型的意思是,当显示该向导页时,会在向导对话框的上方显示title和description信息;而对于Expanded类型,则这部分信息不会显示出来,整个页面的设计完全由开发人员自己控制。显然,在上面的示例中,2、3、4、5页都是属于Standard类型的向导页,而1和6页则属于Expanded类型
以下是一个向导页的类定义以及构造函数的实现例子:
public partial class FirstPage : WizardPage { public FirstPage(Wizard wizard) :base("First Page", "This is the first page.", wizard) { InitializeComponent(); } }
需要注意的是,向导页的构造函数必须是公有(public)的,而且有且只有一个Wizard类型的参数。
向导页中的几个回调函数
在WizardFramework.WizardPage基类中,定义了一些可供Wizard对象调用的回调函数,这些函数将在适当的时候被调用,因此,开发人员可以在这些回调函数中处理自己的逻辑,比如设置是否允许用户点击“下一页”等导航按钮。
- 【方法】Task ExecuteShowAsync(IWizardPage fromPage)
- 当Wizard准备显示当前向导页时,调用此方法。该方法以异步方式调用
- fromPage参数:表示是从哪个向导页导航过来的。比如,当用户点击“下一页”按钮后,下一个向导页将会显示在向导对话框中,通常情况下,fromPage参数是所显示的向导页的上一页
- 【方法】Task<bool> ExecuteBeforeGoingPreviousAsync()
- 当用户点击“上一页”按钮后,向导对话框准备进入上一向导页时,调用此方法。该方法以异步方式调用
- 返回值:返回一个能够返回布尔值的任务对象,此布尔值表示是否真的允许向导对话框进入上一页(True=允许;False=不允许)
- 【方法】Task<bool> ExecuteBeforeGoingNextAsync()
- 当用户点击“下一页”按钮后,向导对话框准备进入下一向导页时,调用此方法。该方法以异步方式调用
- 返回值:返回一个能够返回布尔值的任务对象,此布尔值表示是否真的允许向导对话框进入下一页(True=允许;False=不允许)
- 【方法】Task<bool> ExecuteBeforeGoingFinishAsync()
- 当用户点击“完成”按钮后,向导对话框准备进入“完成”向导页时,调用此方法。该方法以异步方式调用
- 返回值:返回一个能够返回布尔值的任务对象,此布尔值表示是否真的允许向导对话框结束,并向调用方返回DialogResult.OK值(True=允许;False=不允许)
- 【方法】void PersistValuesToModel()
- 当向导对话框准备进入其它向导页时,会调用此方法,将当前已显示的向导页界面上的用户设置保存到向导数据模型对象中,下一节将详细介绍这部分内容
- 【属性】System.Windows.Forms.Control FocusingControl
- 设置在打开当前向导页时,焦点(Focus)所在的界面控件(即焦点默认应该在哪个控件上)
- 【属性】System.Drawing.Image Logo
- 设置当前向导页需要显示在向导对话框右上角的图标
开发人员在自定义自己的向导页面时,可以在子类中重载以上方法或属性,以在不同的时机处理不同的逻辑。详细使用方法,可以参考WizardFramework源代码库自带的InstallerSample示例项目。
向导数据模型
在WizardFramework中,通过引入向导数据模型的概念,来保存每个向导页的用户设置。比如,在InstallerSample示例项目的FeaturePage向导页中,用户可以在界面上选择安装的类型(最小安装、标准安装和完全安装),还可以指定安装路径。这些用户设定都被保存在该向导页的数据模型中,以便其它向导页或者向导对话框读取使用。向导数据模型类的定义,需要实现IWizardModel接口,如下:
public sealed new class Model : IWizardModel { #region Public Properties public string SelectedFeature { get; set; } public string SelectedFolder { get; set; } #endregion Public Properties #region Public Methods public override string ToString() { var sb = new StringBuilder(); sb.AppendLine(string.Format(Resources.SelectedFeaturePattern, SelectedFeature)); sb.AppendLine(string.Format(Resources.InstallationFolderPattern, SelectedFolder)); return sb.ToString(); } #endregion Public Methods }
在数据模型对象中,只需要编写一些与界面控件取值相对应的属性即可。一种推荐的做法是,将向导数据模型类定义在每个向导页的类定义中,也就是作为向导页类的一个内嵌类来定义,类名就简单地使用Model作为类名就行了,为了绕过编译器警告,在声明类的时候加上new关键字。这样做的好处是,今后在其它向导页面或者向导对话框中获取向导模型对象时,有助于提高代码的可读性。
如果向导页需要使用向导数据模型,则需要在构造函数中初始化数据模型对象,如下(注意base构造函数调用的最后一个参数):
public FeaturePage(Wizard wizard) : base(Resources.FeaturePageTitle, Resources.FeaturePageDescription, wizard, new Model()) { InitializeComponent(); }
并且,需要重载PersistValuesToModel方法,以便将界面控件的值保存到数据模型中:
protected override void PersistValuesToModel() { var selectedFeature = string.Empty; if (rbMinimal.Checked) selectedFeature = rbMinimal.Text; else if (rbStandard.Checked) selectedFeature = rbStandard.Text; else if (rbFull.Checked) selectedFeature = rbFull.Text; ModelAs<Model>().SelectedFeature = selectedFeature; ModelAs<Model>().SelectedFolder = txtInstPath.Text; }
当需要在其它页面中,或者通过向导对话框获取向导页的数据模型对象时,可以使用下面的方法:
var model = Wizard.GetWizardModel<FeaturePage.Model>();
此处,通过Wizard对象的GetWizardModel泛型方法,即可得到FeaturePage.Model数据模型对象。
控制向导的导航
在有些场景下,需要根据当前页的某些界面设置,来决定下一页应该导航到哪个向导页。比如,在向导页1中,如果用户点击了某个复选框,那么当用户再点“下一步”按钮时,则跳过页面2,直接进入页面3,否则,则需要跳到页面2。此时,可以调用Wizard对象的SetPageDisplay方法即可。该方法有两个重载:
- void SetPageDisplay(int pageIndex, WizardPageDisplay display)
- pageIndex:根据向导页加入到向导对话框的顺序,所对应的向导页索引号
- display:指定该页是显示(WizardPageDisplay.Show)还是不显示(WizardPageDisplay.Hide)
- void SetPageDisplay<T>(WizardPageDisplay display)
- 泛型类型T:指定需要设置显示行为的向导页类型
- display:指定该页是显示(WizardPageDisplay.Show)还是不显示(WizardPageDisplay.Hide)
开发向导对话框
向导对话框的开发非常简单,只需要新建一个System.Windows.Forms.Form类型,然后使其继承于WizardFramework.Wizard类即可,无需再写更多的代码。当然,如果需要设置一些额外的属性,也可以直接在Visual Studio的属性页中进行设置即可。
初始化向导页,并将向导页添加到向导对话框中
下面的代码展示了向导页初始化并添加到向导对话框的做法,还是非常简单的:
var installer = new FrmInstaller(); installer.Add(installer.CreatePage<WelcomePage>()); installer.Add(installer.CreatePage<LicensePage>()); installer.Add(installer.CreatePage<FeaturePage>()); installer.Add(installer.CreatePage<SummaryPage>()); installer.Add(installer.CreatePage<InstallingPage>()); installer.Add(installer.CreatePage<FinishPage>());
总结
本文介绍了我自己开发的一个向导框架,并介绍了框架的使用。或许,在某些情况下,该框架还是不能满足需求,此时,可以直接把WizardFramework的源代码拉下来进行定制。