Wizard Framework:一個自己開發的基於Windows Forms的嚮導開發框架

dax.net發表於2015-07-01

最近因專案需要,我自己設計開發了一個基於Windows Forms的嚮導開發框架,目前我已經將其開源,併發布了一個NuGet安裝包。比較囧的一件事是,當我釋出了NuGet安裝包以後,發現原來已經有一個.NET的嚮導開發框架了,它叫Microsoft Visual Studio 2013 Wizard Framework。我並沒有對其進行深入研究,單從名稱上看,該框架是否只能在Visual Studio 2013下使用?上網搜尋過,也沒發現微軟有比較詳細的官方資料介紹這個框架。不過無論如何,我還是在此向大家介紹一下我自己開發的這個嚮導框架,也算是讓大家瞭解一下我的設計思路,以及使大家能夠方便地從該框架獲益,快捷地在自己的專案中也用上這個嚮導框架。

有圖有真相

話不多說,請先看效果圖。為了演示這個框架,我依賴它開發了一個模擬軟體安裝過程的嚮導程式。用過類似Install Shield的安裝程式的使用者,應該對下面的這些對話方塊比較熟悉吧:

image

image

image

怎麼樣?看上去還算專業吧?它就是用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開啟)。此時,你可以看到該解決方案包含兩個專案:

image

WizardFramework專案就是該框架的原始碼,而InstallerSample是一個Windows Forms應用程式,它使用了WizardFramework開發了一個模擬軟體安裝過程的嚮導介面,也就是上面你所看到的介面效果了。你可以修改Program.cs中Main函式的第一條語句,將本地化資訊設定為zh-CN或者en-US,來體驗該模擬程式在不同區域語言下的介面效果。

如果你希望在自己的Windows Forms專案中使用Wizard Framework,你可以在專案上單擊滑鼠右鍵,選擇Manage NuGet Packages選單項,在彈出的對話方塊中搜尋WizardFramework關鍵字即可:

image

選中之後,單擊“安裝”按鈕,即可將本嚮導開發框架新增到你的專案中(注意:建議應用程式是基於.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的原始碼拉下來進行定製。

相關文章