從PRISM開始學WPF(五)MVVM(一)ViewModel-更新至Prism7.1
0x5 MVVM
[7.1updated]截止到目前,我們看到7.1的更新主要在三個地方
- PrismApplication ,並且不再使用Bootstrapper
- 更新了unity,現在使用prism.unity作為容易管理
- 更新了IModule介面
下面所有程式碼片段都更新到7.1,並且不再贅述與6.x的區別
蛤蛤,終於到MVVM了。特別是前面的Module,忒難寫,反正大概知道是怎麼用就好了,具體怎麼個容器,怎麼個依賴注入,我也不是很懂,Prism重度依賴容器,哪哪都是,哪哪都是依賴容器注入。
到目前為止,已經知道怎麼去設定Region,怎麼去關聯View,和關聯其他Module裡的View了。那麼接下來就是MVVM啦,★,°:.☆( ̄▽ ̄)/$:.°★ 。
先看Wiki怎麼對MVVM定義的:
MVVM(Model–view–viewmodel)是一種軟體架構模式。
MVVM有助於將圖形使用者介面的開發與業務邏輯或後端邏輯(資料模型)的開發分離開來,這是通過置標語言或GUI程式碼實現的。MVVM的檢視模型是一個值轉換器,[1] 這意味著檢視模型負責從模型中暴露(轉換)資料物件,以便輕鬆管理和呈現物件。在這方面,檢視模型比檢視做得更多,並且處理大部分檢視的顯示邏輯。[1] 檢視模型可以實現
中介者模式,組織對檢視所支援的用例集的後端邏輯的訪問。
Dior不Dior?首先他不是WPF專有的,現在很多前端框架都實現了MVVM模式,像Vue,Angular。那MVVM這麼火,他到底有什麼神奇的地方呢?資料雙向繫結!資料雙向繫結!資料雙向繫結!
我最早在找MVVM框架的時候,其實並不在乎什麼解耦,前後端分離,可測試啥的,我只是受夠了WinForms前臺程式碼中 ShowDetails和SetModel,後來發現MVVM可以實現雙向繫結,資料驅動介面顯示,就著了迷(?′艸`?)。扯遠了,我們來看Prism,怎樣實現MVVM的。
ViewModel及定位
什麼是ViewModel,ViewModel在MVVM中充當了什麼角色?
ViewModel是對應的View(資料和行為)的抽象,View只是ViewModel的一個消費者,那麼還有其他的消費者嗎?當然有了,那就是單元測試(Unit Test),這個後面說。ViewModel為View提供資料上下文(DataContext),簡單的說,你View需要展示的東西,都在我這裡,你需要跟我繫結,包括資料和命令,不然你就是個靜態的。
那怎麼為View指定ViewModel呢,通常情況下,我們是為控制元件指定Datacontext,而Prism為我們提供了更簡單方式,約定。
約定的繫結方式
- Step1 新建一個Wpf專案,新建兩個資料夾Views 和 ViewModels,用來存放View和ViewModel,刪掉MainWindow.xaml,並在Views新建一個新 MainWindow窗體當我們的Shell。
app.xaml.cs:
using Prism.Ioc;
using Prism.Unity;
using System.Windows;
using ViewModelLocator.Views;
namespace ViewModelLocator
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : PrismApplication
{
protected override Window CreateShell()
{
return Container.Resolve<MainWindow>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
}
}
}
- Step2 在ViewModels資料夾內新建,一個MainViewModel的類,繼承BindableBase,注意,這裡是個類(Class)
MainWindowViewModel.cs
using Prism.Mvvm;
namespace ViewModelLocator.ViewModels
{
public class MainWindowViewModel : BindableBase
{
private string _title = "Prism Unity Application";
public string Title
{
get { return _title; }
set { SetProperty(ref _title,value); }
}
public MainWindowViewModel()
{
}
}
}
- Step3 修改
MainWindow.xaml
,覆蓋下面的程式碼:(當前也可以不覆蓋,對比發現,我們這裡只多了一點東西)
<Window x:Class="ViewModelLocator.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
Title="{Binding Title}" Height="350" Width="525">
<Grid>
<ContentControl prism:RegionManager.RegionName="ContentRegion" />
</Grid>
</Window>
Tips:資料繫結Title="{Binding Title}" ,Title是對應ViewModel裡的一個公開屬性。
執行後發現,視窗的Title正式MainWindowViewModel裡Title的值,可是我們並沒有為MainWindow指定ViewModel啊,正常的繫結看上去是應該是這樣
<UserControl.DataContext>
<vm:NumberChangeLogViewModel />
</UserControl.DataContext>
或者這樣
<vw:NumberView
DockPanel.Dock="Top"
DataContext="{Binding Path=Number,Mode=OneTime}"
/>
蛤蛤,剛開始我也很懵逼,可是我愛學習,在Prism的原始碼Prism.Mvvm.ViewModelLocationProvider
中我發現了這個:
/// <summary>
/// ViewModelfactory that provides the View instance and ViewModel type as parameters.
/// </summary>
static Func<object,Type,object> _defaultViewModelFactoryWithViewParameter;
/// <summary>
/// Default view type to view model type resolver,assumes the view model is in same assembly as the view type,but in the "ViewModels" namespace.
/// </summary>
static Func<Type,Type> _defaultViewTypeToViewModelTypeResolver =
viewType =>
{
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);
};
是不是豁然開朗?O(∩_∩)O
我們不一樣,定製約定
約定就是要來被打破的,有人可能覺得字尾加一個ViewModel實在是LowB,我想改變他,可以不可以?當然闊以啦。
prism為我們提供了一個可重寫的ConfigureViewModelLocator的方法來配置ViewModel的定位器,如果你想修改預設的約定為View的名字後面+VM,你可以在app.xaml.cs
這樣寫:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver((viewType) =>
{
var viewName = viewType.FullName;
var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
var viewModelName = $"{viewName}VM,{viewAssemblyName}";
return Type.GetType(viewModelName);
});
}
我就是我,是顏色不一樣的煙火
這世界上不乏個性鮮明的人,你們那些約定和打破的約定還不都是一路貨色。我就要不一樣的,我想跟誰綁在一起就跟誰綁在一起。好,你跟誰好是你的自由,Prism不能限制你,不然你會投訴它不民主。??
如果你想指定你繫結的ViewModel物件又不想遵循一定的規則,你同樣可以在ConfigureViewModelLocator方法中註冊繫結,像下面這樣:
protected override void ConfigureViewModelLocator()
{
base.ConfigureViewModelLocator();
// type / type
//ViewModelLocationProvider.Register(typeof(MainWindow).ToString(),typeof(CustomViewModel));
// type / factory
//ViewModelLocationProvider.Register(typeof(MainWindow).ToString(),() => Container.Resolve<CustomViewModel>());
// generic factory
//ViewModelLocationProvider.Register<MainWindow>(() => Container.Resolve<CustomViewModel>());
// generic type
ViewModelLocationProvider.Register<MainWindow,CustomViewModel>();
}
當然了,在xaml中的
prism:ViewModelLocator.AutoWireViewModel="True"
依舊是必不可少的。