教面試官ReentrantLock源碼
1835
2025-03-31
1. Prism.Wpf 和 Prism.Unity#
這篇是 Prism 8.0 入門的第二篇文章,上一篇介紹了 Prism.Core,這篇文章主要介紹 Prism.Wpf 和 Prism.Unity。
以前做 WPF 和 Silverlight/Xamarin 項目的時候,我有時會把 ViewModel 和 View 放在不同的項目,ViewModel 使用 可移植類庫項目,這樣 ViewModel 就與 UI 平臺無關,實現了代碼復用。這樣做還可以強制 View 和 ViewModel 解耦。
現在,即使在只寫 WPF 項目的情況下,但為了強制 ViewModel 和 View 假裝是陌生人,做到不留后路,我也傾向于把 View 和 ViewModel 放到不同項目,并且 ViewModel 使用 .Net Standard 作為目標框架。我還會假裝下個月 UWP 就要崛起了,我手頭的 WPF 項目中的 ViewModel 要做到平臺無關,方便我下個月把項目移植到 UWP 項目中。
但如果要使用 Prism 構建 MVVM 程序的話,上面這些根本不現實。首先,Prism 做不到平臺無關,它針對不同的平臺提供了不同的包,分別是:
針對 WPF 的 Prism.Wpf
針對 Xamarin Forms 的 Prism.Forms
針對 Uno 平臺的 Prism.Uno
其次,根本就沒有針對 UWP 的 Prism.Windows(UWP 還有未來,忍住別哭)。
所以,除非只使用 Prism.Core,否則要將 ViewModel 項目共享給多個平臺有點困難,畢竟用在 WPF 項目的 Prism.Wpf 本身就是個 Wpf 類庫。
現在“編寫平臺無關的 ViewModel 項目”這個話題就與 Prism 無關了,再把 Prism.Unity 和 Prism.Wpf 選為代表(畢竟這個組合比其它組合下載量多些),這篇文章就只用它們作為 Prism 入門的學習對象。
Prism.Core、Prism.Wpf 和 Prism.Unity 的依賴關系如上所示。其中 Prism.Core 實現了 MVVM 的核心功能,它是一個與平臺無關的項目。Prism.Wpf 里包含了 Dialog Service、Region、Module 和導航等幾個模塊,都是些用在 WPF 的功能。Prism.Unity 本身沒幾行代碼,它表示為 Prism.Wpf 選擇了 UnityContainer 作為 IOC 容器。(另外還有 Prism.DryIoc 可以選擇,但從下載量看 Prism.Unity 是主流。)
就算只學習 Prism.Wpf,可它的模塊很多,一篇文章實在塞不下。我選擇了 Dialog Service 作為代表,因為它的實現思想和其它的差不多,而且彈窗還是 WPF 最常見的操作。這篇文章將通過以下內容講解如何使用 Prism.Wpf 構建一個 WPF 程序:
PrismApplication
RegisterTypes
XAML ContainerProvider
ViewModelLocator
Dialog Service
Prism 的最新版本是 8.0.0.1909。由于 Prism.Unity 依賴 Prism.Wpf,所以只需安裝 Prism.Unity:
Install-Package Prism.Unity -Version 8.0.0.1909
2. PrismApplication
安裝好 Prism.Wpf 和 Prism.Unity 后,下一步要做的是將 App.xaml 的類型替換為 PrismApplication。
上面是修改過的 App.xaml,將 Application 改為 prism:PrismApplication,并且移除了 StartupUri="MainWindow.xaml"。
接下來不要忘記修改 App.xaml.cs:
public partial class App : PrismApplication { public App() { } protected override Window CreateShell() => Container.Resolve
PrismApplication 不使用 StartupUri ,而是使用 CreateShell 方法創建主窗口。CreateShell 是必須實現的抽象函數。PrismApplication 提供了 Container 屬性,CreateShell 函數里通常使用 Container 創建主窗口。
3. RegisterTypes
其實在使用 CreateShell 函數前,首先必須實現另一個抽象函數 RegisterTypes。由于 Prism.Wpf 相當依賴于 IOC,所以要現在 PrismApplication 里注冊必須的類型或依賴。PrismApplication 里已經預先注冊了 DialogService、EventAggregator、RegionManager 等必須的類型(在 RegisterRequiredTypes 函數里),其它類型可以在 RegisterTypes 里注冊。它看起來像這樣:
protected override void RegisterTypes(IContainerRegistry containerRegistry) { // Core Services // App Services // Views containerRegistry.RegisterForNavigation
4. XAML ContainerProvider
在 XAML 中直接實例化 ViewModel 并設置 DataContext 是 View 和 ViewModel 之間建立關聯的最基本的方法:
但現實中很難這樣做,因為相當一部分 ViewModel 都會在構造函數中注入依賴,而 XAML 只能實例化具有無參數構造函數的類型。為了解決這個問題,Prism 提供了 ContainerProvider 這個工具,通過設置 Type 或 Name 從 Container 中解析請求的類型,它的用法如下:
5. ViewModelLocator
Prism 還提供了 ViewModelLocator,用于將 View 的 DataContext 設置為對應的 ViewModel:
在將 View 的 ViewModelLocator.AutoWireViewModel 附加屬性設置為 True 的同時,Prism 會為查找這個 View 對應的 ViewModel 類型,然后從 Container 中解析這個類型并設置為 View 的 DataContext。它首先查找 ViewModelLocationProvider 中已經使用 Register 注冊的類型,Register 函數的使用方式如下:
ViewModelLocationProvider.Register
如果類型未在 ViewModelLocationProvider 中注冊,則根據約定好的命名方式找到 ViewModel 的類型,這是默認的查找邏輯的源碼:
Copy
var viewName = viewType.FullName; viewName = viewName.Replace(".Views.", ".ViewModels."); var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName; var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel"; var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName); return Type.GetType(viewModelName);
例如 PrismTest.Views.MainView 這個類,對應的 ViewModel 類型就是 PrismTest.ViewModels.MainViewModel。
當然很多項目都不符合這個命名規則,那么可以在 App.xaml.cs 中重寫 ConfigureViewModelLocator 并調用 ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver 改變這個查找規則:
protected override void ConfigureViewModelLocator() { base.ConfigureViewModelLocator(); ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) => { var viewName = viewType.FullName.Replace(".ViewModels.", ".CustomNamespace."); var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName; var viewModelName = $"{viewName}ViewModel, {viewAssemblyName}"; return Type.GetType(viewModelName); }); }
6. Dialog Service
Prism 7 和 8 相對于以往的版本最大的改變在于 View 和 ViewModel 的交互,現在的處理方式變得更加易于使用,這篇文章以其中的 DialogService 作為代表講解 Prism 如何實現 View 和 ViewModel 之間的交互。
DialogService 內部會調用 ViewModelLocator.AutoWireViewModel,所以使用 DialogService 調用的 View 無需添加這個附加屬性。
以往在 WPF 中需要彈出一個窗口,首先新建一個 Window,然后調用 ShowDialog,ShowDialog 阻塞當前線程,直到彈出的 Window 關閉,這時候還可以拿到一個返回值,具體代碼差不多是這樣:
var window = new CreateUserWindow { Owner = this }; var dialogResult = window.ShowDialog(); if (dialogResult == true) { var user = window.User; //other code; }
簡單直接有用。但在 MVVM 模式中,開發者要假裝自己不知道要調用的 View,甚至不知道要調用的 ViewModel。開發者只知道要執行的這個操作的名字,要傳什么參數,拿到什么結果,至于具體由誰去執行,開發者要假裝不知道(雖然很可能都是自己寫的)。為了做到這種效果,Prism 提供了 IDialogService 接口。這個接口的具體實現已經在 PrismApplication 里注冊了,用戶通常只需要從構造函數里注入這個服務:
public MainWindowViewModel(IDialogService dialogService) { _dialogService = dialogService; }
IDialogService 提供兩組函數,分別是 Show 和 ShowDialog,對應非模態和模態窗口。它們的參數都一樣:彈出的對話框的名稱、傳入的參數、對話框關閉時調用的回調函數:
void ShowDialog(string name, IDialogParameters parameters, Action
其中 IDialogResult 類型包含 ButtonResult 類型的 Result 屬性和 IDialogParameters 類型的 Parameters 屬性,前者用于標識關閉對話框的動作(Yes、No、Cancel等),后者可以傳入任何類型的參數作為具體的返回結果。下面代碼展示了一個基本的 ShowDialog 函數調用方式:
var parameters = new DialogParameters { { "UserName", "Admin" } }; _dialogService.ShowDialog("CreateUser", parameters, dialogResult => { if (dialogResult.Result == ButtonResult.OK) { var user = dialogResult.Parameters.GetValue
為了讓 IDialogService 知道上面代碼中 “CreateUser” 對應的 View,需要在 'App,xaml.cs' 中的 RegisterTypes 函數中注冊它對應的 Dialog:
containerRegistry.RegisterDialog
上面這種注冊方式需要依賴 ViewModelLocator 找到對應的 ViewModel,也可以直接注冊 View 和對應的 ViewModel:
containerRegistry.RegisterDialog
有沒有發現上面的 CreateUserWindow 變成了 CreateUserView?因為使用 DialogService 的時候,View 必須是一個 UserControl,DialogService 自己創建一個 Window 將 View 放進去。這樣做的好處是 View 可以不清楚自己是一個彈框或者導航的頁面,或者要用在擁有不同 Window 樣式的其它項目中,反正只要實現邏輯就好了。由于 View 是一個 UserControl,它不能直接控制擁有它的 Window,只能通過在 View 中添加附加屬性定義 Window 的樣式:
最后一步是實現 ViewModel。對話框的 ViewModel 必須實現 IDialogAware 接口,它的定義如下:
public interface IDialogAware { ///
一個簡單的實現如下:
public class CreateUserViewModel : BindableBase, IDialogAware { public string Title => "Create User"; public event Action
上面的代碼在 OnDialogOpened 中讀取傳入的參數,在 RaiseRequestClose 關閉對話框并傳遞結果。至此就完成了彈出對話框并獲取結果的整個流程。
自定義 Window 樣式在 WPF 程序中很流行,DialogService 也支持自定義 Window 樣式。假設 MyWindow 是一個自定義樣式的 Window,自定義一個繼承它的 MyPrismWindow 類型,并實現接口 IDialogWindow:
public partial class MyPrismWindow: MyWindow, IDialogWindow { public IDialogResult Result { get; set; } }
然后調用 RegisterDialogWindow 注冊這個 Window 類型。
protected override void RegisterTypes(IContainerRegistry containerRegistry) { containerRegistry.RegisterDialogWindow
這樣 DialogService 將會使用這個自定義的 Window 類型作為 View 的窗口。
7. 結語
這篇文章介紹了如何使用 Prism.Wpf 創建一個 WPF 程序。雖然只介紹了 IDialogService,但其它模塊也大同小異,為了讓這篇文章盡量簡短我舍棄了它們的說明。
如果討厭 Prism.Wpf 的臃腫,或者需要創建面向多個 UI 平臺的項目,也可以只使用輕量的 Prism.Core。
如果已經厭倦了 Prism,可以試試即將發布的 MVVM Toolkit,它基本就是個 MVVM Light 的性能加強版,而且也更時髦。
8. 參考
https://github.com/PrismLibrary/Prism
https://prismlibrary.com/docs/index.html
WPF
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。