Wizard Framework:一個自己開發的基于Windows Forms的向導開發框架
最近因項目需要,我自己設計開發了一個基于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的源代碼拉下來進行定制。