C# WPF MVVM 實戰 - 1


前言

MVVM 就是 Model – View – ViewModel 三組功能(類)分割的設計模式。廢話不多說,不知道的自己上網查。

用 MVVM 我認為最大好處是能對 ViewModel 做單元測試。另外,MVVM 分工也比較明顯,方便安排程序員分組分工進行項目,基本設計有了之后可以各自敲。

這樣的話,寫出來,類(class)最起碼有三個。比如 Window1 作為 View,Window1ViewModel 作為 ViewModel,實際業務類比如 Sales Order 銷售訂單作為 Model。

View 不一定要是 System.Control.Window,UserControl 也可以,Page也行,總之,是 UI 用來顯示用的。

常用基類

有兩個基類,做 MVVM 你有的話會方便一些:

  1. ViewModelBase
  2. RelayCommand / DelegateCommand

我以下代碼都不會列這兩個出來。

ViewModelBase 代碼比較常見,搜索然后抄下來就可以了,然后寫 ViewModel 比如 Window1ViewModel 類時候,繼承自 ViewModelBase 即可。它的主要作用,是提供 OnPropertyChange(string propertyName) 這方法,告訴視圖 View 知道,值發生變化需要更新顯示。它的代碼,比如如下:

/// <summary>
/// Provides common functionality for ViewModel classes
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
    public virtual string DisplayName { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }


    #region IDisposable Members

    public void Dispose()
    {
        this.OnDispose();
    }

    protected virtual void OnDispose()
    {
    }

    #endregion
}

個人愛好,刪掉 DisplayName 或者自己加其他屬性請自便,以它為基類做 ViewModel 時候代碼會簡單一些。

RelayCommand / DelegateCommand 代碼也是比較常見,搜索一下抄下來就是。它是一個實現了 ICommand 接口的類。做命令的綁定,比如 Button 中的 Command 屬性,綁定時它的類型要求是 ICommand 的東西。ICommand 比起點擊事件優勝的地方是,ICommand 除了委托執行方法以外,還有一個 CanExecute 的委托,可以自動 Enable / Disable 按鈕。代碼比如:

/// <summary>
/// Base Relay Command implements ICommand for easy delegation
/// </summary>
public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }
    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members

}

WPF 入門請點這里:十五分鍾 WPF 入門

要注意的雜項

有幾點要注意:

  1. ICommand 一般不傳參數,不是不可以,只是一般來說沒必要。一切值都在 ViewModel 內的時候,你不用讓 View 再告訴你什么新鮮事
  2. 一般來說一個 ViewModel 對一個 View
  3. WPF 的 PasswordBox 做不了綁定,據說是安全性原因,沒辦法
  4. 不一定在 View 的背后代碼一句都不寫才叫做 MVVM,但操作數據的不會在 View 內出現,操作 View 的不會在 ViewModel 出現

示范

來個簡單的示范:

View 部分

  1. <Window x:Class="WPF_Binding_Example.MainView"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="300" Width="300">
  5.    
  6.     <Window.Resources>
  7.         <Style x:Key="myStyle" TargetType="StackPanel">
  8.             <Style.Triggers>
  9.                 <DataTrigger Binding="{Binding Path=Age}"
  10.                             Value="0">
  11.                     <Setter Property="Background"
  12.                            Value="Yellow"/>
  13.                 </DataTrigger>
  14.             </Style.Triggers>
  15.         </Style>
  16.         <DataTemplate x:Key="PersonTemplate">
  17.             <StackPanel Margin="2"
  18.                        Orientation="Horizontal"
  19.                        Style="{StaticResource myStyle}">
  20.                 <TextBlock Text="{Binding Name}"
  21.                           Width="50"/>
  22.                 <TextBlock Text="{Binding Age}"/>
  23.             </StackPanel>
  24.         </DataTemplate>
  25.     </Window.Resources>
  26.    
  27.     <Grid>
  28.         <ListView Margin="38,50,33,59"
  29.                  Name="listView1"
  30.                  ItemsSource="{Binding PersonList}"
  31.                  ItemTemplate="{StaticResource PersonTemplate}"
  32.                  />
  33.         <Button Command="{Binding AddRowCommand}"
  34.                Height="23"
  35.                Width="75"
  36.                HorizontalAlignment="Right"
  37.                Margin="0,0,11,10"
  38.                VerticalAlignment="Bottom"
  39.                Content="加一行" />
  40.     </Grid>
  41. </Window>

畫這界面的同事,只需要知道三件事:

  1. ViewModel 內會有一個叫做 PersonList 的公共類集合(30 行),要用 ListView 顯示,按內容做些樣式
  2. PersonList 類集合內的單個對象,有兩個公共屬性,分別是 Name 和 Age (20、22行),但不需要知道實際是什么類
  3. ViewModel 內會有一個實現了 ICommand 接口的實例引用,名字是 AddRowCommand(33行)

然后,ViewModel 部分

  1. using System.Collections.ObjectModel;
  2. using System.Windows.Input;
  3. using WPF_Binding_Example.Domain;
  4. using WPF_Binding_Example.Infrastructure;
  5.  
  6. namespace WPF_Binding_Example
  7. {
  8.     public class MainViewModel : ViewModelBase
  9.     {
  10.         public MainViewModel()
  11.         {
  12.             personList = new ObservableCollection<Person>();
  13.  
  14.             //演示用
  15.             Add_Dummy_Data();
  16.         }
  17.  
  18.         ///<summary>
  19.         /// 演示用
  20.         ///</summary>
  21.         private void Add_Dummy_Data()
  22.         {
  23.             PersonList.Add(
  24.                 new Person { Name = "張三", Age = 26 }
  25.                 );
  26.             PersonList.Add(
  27.                 new Person { Name = "李四", Age = 24 }
  28.                 );
  29.         }
  30.  
  31.         private ObservableCollection<Person> personList;
  32.         public ObservableCollection<Person> PersonList
  33.         {
  34.             get { return personList; }
  35.         }
  36.  
  37.         private RelayCommand addRowCommand;
  38.         public ICommand AddRowCommand
  39.         {
  40.             get
  41.             {
  42.                 if (addRowCommand == null)
  43.                 {
  44.                     addRowCommand =
  45.                         new RelayCommand(x => this.AddRow());
  46.                 }
  47.                 return addRowCommand;
  48.             }
  49.         }
  50.  
  51.         private void AddRow()
  52.         {
  53.             this.PersonList.Add(
  54.                 new Person { Name = "我是新人", Age = 0 }
  55.                 );
  56.         }
  57.     }
  58. }

負責這個類的同事,不需要知道界面的任何東西,這類的代碼也非常簡單。

ObservableCollection 在 System.Collections.ObjectModel 內,它與其他集合的分別是,集合有變化時,比如加減 Item 等,它會發出通知告訴視圖。如果你有自己很有個性的 Collection,要做綁定的話,讓它實現  INotifyCollectionChanged 和 INotifyPropertyChanged 即可。

RelayCommand 上面說了,是實現了 ICommand,所以出現了 44-47 行這樣的寫法。剛才說了 ICommand 還有個 CanExecute 的委托,這里沒用到。RelayCommand 的設計是,構造函數參數只有一個方法委托時候,CanExecute 默認返回 True,即永遠可執行。

 

然后是 Model 部分

  1. namespace WPF_Binding_Example.Domain
  2. {
  3.     public class Person
  4.     {
  5.         private string name;
  6.         public string Name
  7.         {
  8.             get { return name; }
  9.             set
  10.             {
  11.                 if (value != name)
  12.                 {
  13.                     name = value;
  14.                 }
  15.             }
  16.         }
  17.  
  18.         private int age;
  19.         public int Age
  20.         {
  21.             get { return age; }
  22.             set
  23.             {
  24.                 if (value != age)
  25.                 {
  26.                     age = value;
  27.                 }
  28.             }
  29.         }
  30.     }
  31. }

 

View 連接 ViewModel

說了半天,除了 ViewModel 和 Model 在上面代碼有點關系以外,View 不認識 ViewModel,ViewModel 不認識 View,怎樣連在一起?

這樣:

  1. using System.Windows;
  2.  
  3. namespace WPF_Binding_Example
  4. {
  5.     ///<summary>
  6.     /// Interaction logic for App.xaml
  7.     ///</summary>
  8.     public partial class App : Application
  9.     {
  10.         protected override void OnStartup(StartupEventArgs e)
  11.         {
  12.             base.OnStartup(e);
  13.             MainViewModel vm = new MainViewModel();
  14.             MainView view = new MainView();
  15.             view.DataContext = vm;
  16.             view.Show();
  17.         }
  18.     }
  19. }

請把 WPF 新建項目時,模板自帶 app.xaml 的 XAML 代碼中 StartupUri 屬性刪除,然后在 App 類重寫 OnStartup 方法,如上。

把 View 和 ViewModel 連在一起的,就一句 view.DataContext = vm;  有人會用 view 構造函數注入 ViewModel,也可以反過來在 ViewModel 構造函數注入 view,然后在 IoC 注冊后直接 resolve 出來用,或者寫在 XAML 內也行,但都離不開這一句 DataContext = vm。本例子中是在 Application 類重寫 OnStartup 方法,把兩者創建實例,然后 view.Show 來實現。

接下去會介紹怎樣單元測試,和各種情景各種數據結構和控件,怎樣用 MVVM 模式做綁定。下一篇,是采購訂單做法的一個示例。

我在這群里,歡迎加入交流:
.Net 開發交流 595769918 .Net 開發交流 595769918

開發板玩家群 578649319開發板玩家群 578649319
硬件創客 (10105555)硬件創客 (10105555)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM