zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    使用ECMAScript 6 模块封装代码
    JavaScript生成一个不重复的ID
    利用setenv进行tomcat 内存设置
    使用Nginx、Nginx Plus防止服务器DDoS攻击
    【Nginx】实现负载均衡的几种方式
    一台Linux服务器可以负载多少个连接?
    Linux配置使用SSH Key登录并禁用root密码登录
    Spring JPA事务
    使用SVN钩子强制提交日志和限制提交文件类型
    RabbitMQ 初学及其深入学习推荐的一些文章
  • 原文地址:https://www.cnblogs.com/leptonation/p/2422575.html
Copyright © 2011-2022 走看看