• MVVM在WPF中的应用


    Binding用于绑定控件属性的值。


    Binding的模型

    从Binding模型中可以看出,Binding对象作为目标和源之间的桥梁,除了著名的双向绑定特征外,WPF还在Binding中添加了一些机制方便我们更加方便的处理数据,比如校验器和转换器。


    下面就来看看Binding对象到底实现了哪些属性?

    1.TargetObject和Property属性默认不需要设置,在设置Binding的时候,WPF框架本身会根据你设置的属性及其控件自动引用。

    2.Source属性设置:绑定一个数据源,一般是一个对象。常用的是DataContext和ItemSource。

    ①DataContext:每一个UI元素都有一个DataContext属性,这个属性属于依赖属性,也就是他是依赖在UI控件树的,当前控件没有指定DataContext时就会去父控件继续绑定。一般我们会将ViewModel赋给DataContext。

    ②ItemSource:列表控件绑定的对象。

    另外还有XML数据、RelativeSource(相对自身可以指定层级的数据源)等形式绑定数据源。

    3.Path属性:绑定数据源内的属性。

    4.ElementName可以指定绑定UI元素为源,Converter转换器(数据源的属性类型转换成目标属性的类型),Validation校验器(设置一些规则校验数据),Mode绑定模式。

    示例:

    (一)ElementName、Mode

    <StackPanel Orientation="Vertical" VerticalAlignment="Center">
        <TextBox x:Name="box1" />
        <TextBox x:Name="box2" Text="{Binding ElementName=box1,Path=Text,Mode=TwoWay}"/>
    </StackPanel>
    

    运行此示例,可以发现虽然Mode绑定为TwoWay模式,还是只能实现box2随box1内容同步,而在box1获取焦点时,才会同步box2的内容。从而看出,TwoWay并不是表示实时双向绑定。

    可以选择box1和box2相互绑定的方式实现双向绑定。

    <StackPanel Orientation="Vertical" VerticalAlignment="Center">
        <TextBox x:Name="box1" Text="{Binding ElementName=box2,Path=Text}"/>
        <TextBox x:Name="box2" Text="{Binding ElementName=box1,Path=Text}"/>
    </StackPanel>
    

    (二)Source、Converter

    首先我们先新建一个Student的Model及Gender枚举类型表示学生的性别。

     public class Student
     {
         public int Number { get; set; }
         public string Name { get; set; }
         public Gender Gender { get; set; }
         public string Address { get; set; }
     }
    
     public enum Gender
     {
         Male,
         Female
     }
    

    界面需要展示学生的编号、姓名、性别及住址,我们可以使用Binding绑定相应TextBlock的Text属性。

    	<Window.Resources>
            <Style TargetType="TextBlock">
                <Setter Property="Height" Value="30"/>
                <Setter Property="Width" Value="300"/>
                <Setter Property="Margin" Value="5"/>
                <Setter Property="HorizontalAlignment" Value="Center"/>
            </Style>
        </Window.Resources>
        <Grid>
            <StackPanel Orientation="Vertical" VerticalAlignment="Center">
                <TextBlock Text="{Binding Number}"/>
                <TextBlock Text="{Binding Name}"/>
                <TextBlock Text="{Binding Gender}"/>
                <TextBlock Text="{Binding Address}"/>
            </StackPanel>
        </Grid>
    

    在后台代码中,我们只需要在构造当前Window时,给当前Window的DataContext属性添加一个Student类型的Model,这个Model可以来自http请求或其他的控件、本地数据库等等。

     public MainWindow()
     {
          InitializeComponent();
          DataContext = new Student
     	  {
               Number = 1,
               Name = "张三",
               Gender = Gender.Male,
               Address = "福州市"
          };
     }
    

    运行程序,看到我们正确的在界面显示了绑定的属性的值:

    可以看到,枚举类型并没有正确的显示成我们想要的男或女的文本形式,这时候我们就可以用到Converter。新建一个类,继承IValueConverter。

        public class GenderToStringConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                var gender = (Gender)value;
                switch (gender)
                {
                    case Gender.Male:
                        return "男";
                    case Gender.Female:
                        return "女";
                }
                return null;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                throw new NotImplementedException();
            }
        }
    

    在界面中需要在Resources中引用,并设置一个key,然后在相应的Binding中添加Converter引用。

    <Window.Resources>
        <local:GenderToStringConverter x:Key="GenderToStringConverter"/>
        <Style TargetType="TextBlock">
            <Setter Property="Height" Value="30"/>
            <Setter Property="Width" Value="300"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style>
    </Window.Resources>
    <Grid>
        <StackPanel Orientation="Vertical" VerticalAlignment="Center">
            <TextBlock Text="{Binding Number}"/>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text="{Binding Gender,Converter={StaticResource GenderToStringConverter}}"/>
            <TextBlock Text="{Binding Address}"/>
        </StackPanel>
    </Grid>
    

    这下按照我们的想法正确的展示了。


    有这样一个需求,在同一个界面中,有多个地方都Binding了Student的Name属性,那么我希望后台更改Student对象的Name值的时候,前台界面所绑定的地方全部都要更新。
    现在我们实现了Binding数据,但是如果数据有变化,我们界面上的相应属性并不会实时的发生变化,这时候我们就需要引入一个ViewModel层连接View和Model,使View和Model能够实时双向通信。ViewModel继承INotifyPropertyChanged,用于通知属性变化。

    INotifyPropertyChanged用于通知属性变化

    用于通知属性更改的接口,需要实现PropertyChanged事件,一般常在属性的set访问器中。做到当属性值发生改变之后,通知相应名称的属性的属性值变化。

    ①新建一个MainWindowViewModel
    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _name;
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
                }
            }
        }
    }
    
    ②View界面代码修改成
    <StackPanel Orientation="Vertical" VerticalAlignment="Center">
          <TextBlock Text="{Binding Name}"/>
          <TextBox Text="{Binding Name}" TextChanged="TextBox_TextChanged"/>
          <TextBox Text="{Binding Name}" TextChanged="TextBox_TextChanged"/>
    </StackPanel>
    
    ③View.cs后台界面代码为
    public partial class MainWindow : Window
    {
        private MainWindowViewModel MainWindowViewModel;
        public MainWindow()
        {
            InitializeComponent();
            MainWindowViewModel = new MainWindowViewModel();
            DataContext = MainWindowViewModel;
        }
    
        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (sender is TextBox textBox)
            {
                 MainWindowViewModel.Name = textBox.Text;
            }
        }
    }
    
    最终我们实现效果

    可以看到我们成功地实现了双向绑定,这使得所有绑定ViewModel中Name的属性都会实时更新,并且在后台代码中我们并没有去改变View的值,试想,我们从服务端获取到一个新的Model数据,那么我们只需要把新Model的属性赋值给ViewModel中的对应属性,ViewModel就会更新界面元素

    至此,我们在WPF中利用Binding和INotifyPropertyChanged成功实现了MVVM模式,View和Model完全解耦,相比于MVP模式中Presenter接管所有而言,MVVM将界面交互部分逻辑移植到ViewModel中,减轻了Presenter过于繁琐的问题。
  • 相关阅读:
    浅析MySQL二进制日志
    MySQL升级
    浅析MySQL复制
    MySQL关于exists的一个bug
    TokuDB存储引擎
    MySQL中RESET SLAVE和RESET MASTER的区别
    MySQL半同步复制
    MySQL线程池
    分析MariaDB初始化脚本mysql_install_db
    Python装饰器
  • 原文地址:https://www.cnblogs.com/codexiaoyi/p/13166818.html
Copyright © 2020-2023  润新知