1. 程式人生 > WINDOWS開發 >從PRISM開始學WPF(五)MVVM(一)ViewModel-更新至Prism7.1

從PRISM開始學WPF(五)MVVM(一)ViewModel-更新至Prism7.1

原文:從PRISM開始學WPF(五)MVVM(一)ViewModel-更新至Prism7.1

0x5 MVVM

[7.1updated]截止到目前,我們看到7.1的更新主要在三個地方

  1. PrismApplication ,並且不再使用Bootstrapper
  2. 更新了unity,現在使用prism.unity作為容易管理
  3. 更新了IModule介面
    下面所有程式碼片段都更新到7.1,並且不再贅述與6.x的區別

蛤蛤,終於到MVVM了。特別是前面的Module,忒難寫,反正大概知道是怎麼用就好了,具體怎麼個容器,怎麼個依賴注入,我也不是很懂,Prism重度依賴容器,哪哪都是,哪哪都是依賴容器注入。

到目前為止,已經知道怎麼去設定Region,怎麼去關聯View,和關聯其他Module裡的View了。那麼接下來就是MVVM啦,★,°:.☆( ̄▽ ̄)/$:.°★

先看Wiki怎麼對MVVM定義的:

MVVMModel–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"

依舊是必不可少的。