[WPF] MVVM Plugin模式

摘要:[WPF] MVVM Plugin模式

動機 :

Plugin是在軟體系統內增加功能的功能。
如果在軟體系統加入Plugin功能,能提高軟體系統的重用性。


加入Plugin功能的軟體系統在開發完成之後。
如果需要額外加入功能,不用變更已完成的軟體系統就能加入新功能。
並且因為不用變更已完成的軟體系統,也就避免了修改軟體系統會產生的風險。


在MVVM的架構下View跟ViewModel各自獨立,做Plugin功能也就變得比較複雜。
必須要View跟ViewModel各自都有Plugin功能然後再互相組合,才能完成MVVM Plugin的功能。


本篇文章記錄在WPF上,如何實做MVVM Plugin。
為自己做個紀錄,也希望能幫助到有需要的開發人員。


結構 :

首先我們來看下圖,說明了MVVM Plugin的結構。
圖中說明了整個結構包含的專案,並且也可以看到它們之間的相依關係。
而MvvmPlugin.dll是整個Plugin的主要系統,MvvmPlugin.View與MvvmPlugin.ViewModel則是,主要系統之外要掛載的部份。



接著看看下面兩張圖,說明了MvvmPlugin.dll的物件跟畫面。
由圖中可以看出來,整個專案裡的物件。
可以分成兩大類,分別代表MVVM的View跟ViewModel。
代表View的部份負責實做,畫面如何呈現的功能。
代表ViewModel的部份是實做,畫面要呈現甚麼。
在View跟ViewModel之間,則是用WPF的Binding功能來做連接。
而在ViewModel裡面,也可以看到是由AnchorViewModel來負責建立IWorkspaceViewModel。
並且表單畫面會劃分為兩個區塊,


左邊區塊裡的<<WPF ItemsControl>>
-顯示所繫結的所有<<class>>AnchorViewModel。
-只要增加<<class>>AnchorViewModel的數量,就會增加畫面上的按鈕數量。
-使用<<WPF ItemTemplate>>來顯示繫結的<<class>>AnchorViewModel。


右邊區塊裡的<<WPF ContentControl>>
-則會顯示所繫結的一個<<inteface>>IWorkspaceViewModel。
-只要換掉<<inteface>>IWorkspaceViewModel的物件,就會變更畫面上的表單。


 


 

回頭看上一段落右邊區塊的說明,會發現沒有描述<<inteface>>IWorkspaceViewModel採用甚麼Template來顯示。
我們另外再參考下一張的圖片說明,
可以看到<<WPF ContentControl>>,使用外部Template來顯示繫結的<<inteface>>IWorkspaceViewModel。
至於系統裡有哪些外部Template、<<inteface>>IWorkspaceViewModel要用外部Template來顯示,則是由<<xaml>>MainWindow.view.config.xaml所設定。
而有哪些外部用來生成<<inteface>>IWorkspaceViewModel的<<class>>AnchorViewModel要顯示,則是由<<xaml>>MainWindow.viewmodel.config.xaml來設定。
在這兩個檔案資料內加入新增功能的資料,就可以完成MVVM Plugin要加入功能的功能。


實做 :

範列下載 :

範例的程式碼較多,實做說明請參照範例程式內容。
範例程式點此下載


MvvmPlugin.dll :

MvvmPlugin.dll是整個系統的主要結構。
提供了可執行的應用程式外殼,並且也開放Plugin的功能用來掛載系統。
主要參與者有:


MainWindowViewModel
-整個系統的主要ViewModel。
-提供AnchorCollection及Workspace,給MainWindowView.xaml做Binding。
-收到AnchorViewModel發出的WorkspaceViewModel事件時,用新的WorkspaceViewModel替換舊的。
-使用XamlReader頗析外部MainWindow.viewmodel.config.xaml檔案,當作AnchorCollection內容的來源。

//使用XamlReader頗析外部MainWindow.viewmodel.config.xaml檔案,當作AnchorCollection內容的來源。
private IEnumerable<AnchorViewModel> CreateAnchorCollection(string anchorConfigFile)
{
        #region Require

    if (string.IsNullOrEmpty(anchorConfigFile) == true) throw new ArgumentNullException();

    #endregion

    // Result
    List<AnchorViewModel> anchorList = new List<AnchorViewModel>();

    // Create
    ResourceDictionary resourceDictionary = this.CreateResourceDictionary(anchorConfigFile);
    foreach (object resource in resourceDictionary.Values)
    {
        AnchorViewModel anchorViewModel = resource as AnchorViewModel;
        if (anchorViewModel != null)
        {
            anchorViewModel.WorkspaceOpened += delegate(IWorkspaceViewModel workspace)
            {
                IWorkspaceViewModel oldWorkspace = this.Workspace;
                this.Workspace = workspace;
                if (oldWorkspace != null) oldWorkspace.Dispose();
            };
            anchorList.Add(anchorViewModel);
        }
    }

    // return
    return anchorList;
}

private ResourceDictionary CreateResourceDictionary(string resourceDictionaryFile)
{
    #region Require

    if (string.IsNullOrEmpty(resourceDictionaryFile) == true) throw new ArgumentNullException();

    #endregion

    // Require
    if (File.Exists(resourceDictionaryFile) == false) throw new ArgumentException(string.Format("File is not existed : {0}", resourceDictionaryFile));

    // Create ResourceDictionary            
    FileStream fileStream = new FileStream(resourceDictionaryFile, FileMode.Open);
    ResourceDictionary resourceDictionary = XamlReader.Load(fileStream) as ResourceDictionary;

    // Return
    return resourceDictionary;
}

MainWindow.xaml
-整個系統的主要View。
-直接在xaml內,建立MainWindowViewModel並且綁定。
-與MainWindowViewModel提供的AnchorCollection及Workspace,做Binding。
-參考外部ResourceDictionary的方式,讀取MainWindow.view.config.xaml,作為Binding WorkspaceViewModel的Template。

<!--參考外部ResourceDictionary的方式,讀取MainWindow.view.config.xaml-->
<Window.Resources>
    <ResourceDictionary Source="pack://siteoforigin:,,,/MainWindow.view.config.xaml" />
</Window.Resources>

<!--直接在xaml內,建立MainWindowViewModel並且綁定-->
<Window.DataContext>
    <MvvmPlugin:MainWindowViewModel />
</Window.DataContext>

IWorkspaceViewModel
-掛載進MvvmPlugin.dll的外部WorkspaceViewModel,要實做的介面。
-與外部Template做Binding來顯示。


AnchorViewModel
-掛載進MvvmPlugin.dll的外部AnchorViewModel,要實做的介面。
-提供Title、ExecuteCommand,給MainWindowView.xaml做Binding。
-觸發時建立新的WorkspaceViewModel,發出事件通知MainWindowViewModel。


MvvmPlugin.ViewModel.dll、MvvmPlugin.View.dll :

MvvmPlugin.ViewModel.dll是要掛載的ViewModel實做,職責除了提供要掛載進系統的功能之外也負擔了如何生成的職責。
MvvmPlugin.ViewModel.dll是要掛載的View實做,職責主要是提供Binding的ViewModel的外觀。
之所以沒有將這兩個專案做合併,是為了突顯在MVVM架構下View是可以獨立做抽換的。
主要參與者有:


AAnchorViewModel
-負責生成要掛載的ViewModel


AWorkspaceViewModel
-實際要掛載的ViewModel


AWorkspaceView.xaml
-實際要掛載的View


後記 :

在這個模式裡,其實還少了一塊Model的Plugin功能。
因為Model的Plugin這個功能,相對於MVVM Plugin是比較獨立的模式。
在後續的文章裡,將會有Model Plugin的獨立介紹。

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。