美文网首页
浅谈WPF的数据绑定,路由事件和MVVM模式

浅谈WPF的数据绑定,路由事件和MVVM模式

作者: quchangTJU | 来源:发表于2020-01-03 22:14 被阅读0次

    注:本文出现的所有代码为了简单明了均省略了很多细节部分,只注重原理,直接复制粘贴运行得不到对应的结果。

    WPF的数据驱动理念

    传统的winform开发方式是事件驱动模式,比如点击一个按钮,激发对应的事件函数,从而操纵数据和其他控件,这种开发方式有一个潜在的弊端,就是当一个界面中显示逻辑比较复杂,控件较多时,开发工作非常繁琐,比如动态生成一个100项的列表,列表中还有按钮、勾选框、文本框等控件,事件处理函数不仅要控制控件的状态,还要进行数据操控,开发和维护难度变大。

    WPF的数据驱动模式免去了上述的winform开发模式中的很多缺点,首先,每个控件直接和数据对应,控件和控件之间不进行任何交互,其次,控件操控数据进行更改后,数据与对应的控件进行数据绑定,数据更改,前端即时刷新。那么,如果数据数量不是固定的而是动态的,比如一个列表,事先不知道有多少项,要根据列表项动态生成控件,怎么办呢,答案是WPF精心设计的数据模板。

    winform和WPF相比的话简单来说就是下图: winform开发模式 WPF开发模式

    WPF中的数据绑定

    要实现数据驱动,则需要手动将数据和控件进行绑定,什么样的数据格式可以进行绑定呢,答案是依赖属性或者特殊的集合比如ObservableCollection<T>,

    一个典型的依赖属性如下:

    public string DemoStr
        {
            get { return (string)GetValue(DemoStrProperty); }
            set { SetValue(DemoStrProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for DemoStr.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DemoStrProperty =
            DependencyProperty.Register("DemoStr", typeof(string), typeof(MainWindow));
    
    

    依赖属性定义了属性名称(DemoStr),属性类型(string),所属的类(MainWindow),我们就可以将它当成普通属性进行赋值,修改了,如果要绑定到文本框的内容上,实现数据变动时文本框实时变动,需要在xaml文件中进行定义:

    <Label x:Name="label1" Content="{Binding DemoStr}"/>
    

    此时还没有完,我们需要定义前端的控件是与哪一个对象绑定,所以要定义绑定的对象,如果直接在窗口后台代码文件中定义的依赖属性,则可以直接将当前窗口对象定义为控件的数据来源:

    label1.DataContext = this;
    

    所以,基本的数据绑定有三步:

    1. 设计好需要绑定的数据
    2. 在控件上设置好需要绑定的属性名称
    3. 设置控件的数据来源

    需要转换数据格式的数据绑定

    很多时候数据不是直接显示到前端的,比如一个表示成绩是否及格的bool数据,在前端应该以红色方格或者绿色方格来显示(及格为绿色,不及格为红色),这时就涉及到了数据转换,数据转换需要专门定义一个定义数据转换类,而且要实现IValueConverter接口,官方有示例,这里不再赘述,示例链接地址:https://docs.microsoft.com/zh-cn/previous-versions/visualstudio/visual-studio-2010/dd434207(v=vs.100)

    路由事件与传统winform事件的区别

    传统的winform事件的触发对象只能是控件本身,而路由事件可以将触发对象沿着控件数向上或者向下传递,比如一个grid网格控件内有多个按钮控件,可以设置grid拦截这些按钮的点击并处理,这在winform上是无法实现的。下面的代码示例是一个grid网格控件拦截到内部的按钮点击事件:

    <Grid x:Name="grid" Button.Click ="Grid_Click" >
            <Button Content="ShowMe"/>
    </Grid>
    

    后台代码文件如下:

    private void Grid_Click(object sender, RoutedEventArgs e)
            {
                Button button = e.OriginalSource as Button;//通过e.OriginalSource得到点击的按钮,注意不是sender参数
                MessageBox.Show(button.Content.ToString());//弹窗显示ShowMe
            }
    

    上面的示例中,sender参数是grid控件,代表事件的拥有者,我们让grid控件响应Grid_Click函数,所以sender就是grid控件,而想要得到点击的控件,需要使用e.OriginalSource属性并转化为对应的控件格式。

    对比在传统的winform开发中,如果需要动态生成按钮,并且让按钮进行数据或者空间操作是相当麻烦的,有很多时候还需要根据给控件命名然后查找比如button1,button2button3...来进行对应的事件处理,而在WPF开发中,通过路由事件,则能使一个父级控件监控所有子控件的各种事件来进行集中处理。

    数据绑定的精华:数据模板

    前文提到的动态生成列表,适用于后端数据为集合的情况。在传统的winform中,由于数据项未知,所以要使用代码来进行动态生成控件,根据给控件命名来访问控件也十分繁琐,WPF彻底解决了这个问题,那就是在xaml中定义数据模板来控制数据如何显示在前端,下面是一个数据模板的示例:

            <local:BoolToBrushConverter x:Key="boolToBrushConverter" />
    
            <DataTemplate x:Key="listBoxTemplate">
                <StackPanel Orientation="Horizontal" >
                    <Rectangle Fill="{Binding Path=IsEnrolled, 
                        Converter={StaticResource boolToBrushConverter}}"/>
                    <TextBlock Text="{Binding Path=StudentName}"/>
                    <Button Content="弹出名字" Click="btn_Click" />
                </StackPanel>
            </DataTemplate>
    
    <Grid x:Name="grid">
            <ListBox x:Name="listbox" 
                     ItemsSource="{Binding}"  
                     ItemTemplate="{Binding Mode=OneWay, Source={StaticResource listBoxTemplate}}"/>
    </Grid>
    

    上面引入了一个boolToBrushConverter转换器,作用是将数据中的bool数据转化为红色和绿色的画刷格式,这样前端就可以根据数据的true或者false来显示红色或者绿色,具体代码见前文的官方教程,这里省略,然后我们定义了一个数据模板,定义了一个数据项如何进行显示,其中有一个颜色方块显示红色或者绿色,TextBlock显示StudentName属性,还有一个按钮来响应btn_Click事件,然后在grid控件中定义了一个ListBox控件,用来显示我们的数据,然后看一下后台代码:

    //我们要绑定的数据类型
    public class Student
        {
            public Student(){}
            public Student(string name, bool isenrolled)
            {
                StudentName = name;
                IsEnrolled = isenrolled;
            }
            public string StudentName { get; set; }
            public bool IsEnrolled { get; set; }
        }
        public class StudentList : ObservableCollection<Student>
        {
        }
    //窗口后台代码
    public partial class MainWindow : MetroWindow
        {
            StudentList students = new StudentList();//实例化我们的数据
    
            public MainWindow()
            {
                InitializeComponent();
                //添加几项数据
                students.Add(new Student("Syed Abbas", false));
                students.Add(new Student("Lori Kane", true));
                students.Add(new Student("Steve Masters", false));
                students.Add(new Student("Tai Yee", true));
                //为listbox绑定数据源
                listbox.DataContext = students;
            }
            //数据模板中的按钮事件处理函数
            private void btn_Click(object sender, RoutedEventArgs e)
            {
                //通过e.OriginalSource拿到点击的按钮
                var button = e.OriginalSource as FrameworkElement;
                //通过TemplatedParent属性拿到所在的模板生成控件
                var cp = button.TemplatedParent as ContentPresenter;
                //通过Content属性拿到绑定的单个数据实例
                Student stu = cp.Content as Student;
                //显示该数据实例的StudentName属性
                MessageBox.Show(stu.StudentName);
            }
    }
    

    前端界面效果如下:

    前端列表控件

    单击按钮后,触发了提示框:

    点击按钮

    至此,我们可以得出一个WPF的开发流程:
    1.利用xaml搭建前端界面,同时在xaml中设定好控件要绑定的属性、样式以及数据模板、事件、命令、动画等
    2.构建我们的数据格式,可以设计成一个ViewModel类来适配前端需要绑定的数据格式,如果是单个属性,则在类中定义依赖属性,如果是集合数据,则需使用ObservableCollection<T>等特殊的集合。
    3.在后台代码文件中实例化我们定义的ViewModel类,并且指定前端的DataContext为我们的ViewModel类实例
    4.编写事件处理函数,根据控件行为处理数据

    从上面的步骤中,我们可以看到控件之间的状态不再互相靠事件函数进行更新,而是每个控件根据自己绑定的数据进行状态更新,我们的程序只专注于数据的状态,这就是数据驱动的理念,传统的MVC模式是Model(数据)View(显示)Control(控制),在winform中,随着程序的复杂,Control也随之变得复杂(各种事件处理函数同时控制view和model),在WPF中,数据绑定自动完成了控件状态的更新,MVC也转化为MVVM,即Model(数据)View(显示,即xaml)ViewModel(数据显示层,即我们用来适配前端的ViewModel类和前端用来更改ViewModel数据的事件处理函数)。


    MVC和MVVM的区别

    相关文章

      网友评论

          本文标题:浅谈WPF的数据绑定,路由事件和MVVM模式

          本文链接:https://www.haomeiwen.com/subject/dmizoctx.html