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

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

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的区别
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 目录 什么是WPF? WPF的历史? 为什么要用WPF及WPF作用 WPF与winForm区别? 什么是WPF? ...
    灬52赫兹灬阅读 5,798评论 2 11
  • 什么是Xaml Xaml(Extensible Application Markup Language) 可扩展应...
    北风知我意阅读 648评论 0 1
  • 一、与WPF结缘 第一次接触WPF是在2012年。学习了WinForm编程之后,了解到WPF这个非常先进的UI框架...
    金戈大王阅读 1,455评论 0 8
  • 2019年7月7日 星期天 一直以来,我都有一个愿望——我想把我一头的头发给全部剃掉。原因,也没有什么特别的,...
    Sernedipity阅读 200评论 0 3
  • 吸天地之灵气 饮朝夕之露滴 嚼日月之精华 慢悠悠 将匆匆岁月拉扯成 长长的 长长的丝 织就硕大的网 网住那温...
    边城涛哥阅读 529评论 2 21