写在前面
本文将会介绍WPF如何实现前后端数据绑定和在进行数据绑定时常用的方法和类以及对于DataGrid、ListView这样的控件重写数据模板后控件如何进行数据绑定。
本文主要针对于数据绑定的基础实现进行介绍,通过此博文你将会有能力编写一个MVVM设计模式的C#、WPF项目。如果您是C#及WPF的资深开发人员本文可能对您没有太大的帮助,但如果你是一个正在学习和了解C#、WPF的开发人员来说本文可以帮助你认识MVVM设计模式和数据绑定。.
一、实现前后端数据绑定:
说到前后端的数据绑定,就需要先说一下WPF的MVVM设计模式,它是由传统的MVC设计模式改进而来,不同点在于MVVM数据源更新不需要一个Controller控制器来向前台同步数据,同时前台数据更改也不需要控制器向后台同步。如果想深入详细的了解MVVM设计模式百度百科对这部分的讲解和说明我认为非常的详细和系统。也可以阅读下方的实例,相信通过代码实例更能够让你对MVVM有一个更深入的认识。
实例:
源代码地址(码云):https://gitee.com/hkb1202/csharp-wpf-data-binding-demo
实例基于.Net Core 3.1平台,为博主编写并亲测可用的,通过实例相信你可以更好的理解这部分内容。

新建一个WPF项目,并且添加Command类和MainWindowsViewModel类
Command.cs代码:
1 using System;2 using System.Windows.Input;34 namespace WpfExample5 {6 public class Command : ICommand7 {89 /// <summary>10 /// 检查命令是否可以执行的事件,在UI事件发生导致控件状态或数据发生变化时触发11 /// </summary>12 public event EventHandler CanExecuteChanged13 {14 add15 {16 if (_canExecute != null)17 {18 CommandManager.RequerySuggested += value;19 }20 }21 remove22 {23 if (_canExecute != null)24 {25 CommandManager.RequerySuggested -= value;26 }27 }28 }2930 /// <summary>31 /// 判断命令是否可以执行的方法32 /// </summary>33 private Func<object, bool> _canExecute;3435 /// <summary>36 /// 命令需要执行的方法37 /// </summary>38 private Action<object> _execute;3940 /// <summary>41 /// 创建一个命令42 /// </summary>43 /// <param name="execute">命令要执行的方法</param>44 public Command(Action<object> execute) : this(execute, null)45 {4647 }4849 /// <summary>50 /// 创建一个命令51 /// </summary>52 /// <param name="execute">命令要执行的方法</param>53 /// <param name="canExecute">判断命令是否能够执行的方法</param>54 public Command(Action<object> execute, Func<object, bool> canExecute)55 {56 _execute = execute;57 _canExecute = canExecute;58 }5960 /// <summary>61 /// 判断命令是否可以执行62 /// </summary>63 /// <param name="parameter">命令传入的参数</param>64 /// <returns>是否可以执行</returns>65 public bool CanExecute(object parameter)66 {67 if (_canExecute == null) return true;68 return _canExecute(parameter);69 }7071 /// <summary>72 /// 执行命令73 /// </summary>74 /// <param name="parameter"></param>75 public void Execute(object parameter)76 {77 if (_execute != null && CanExecute(parameter))78 {79 _execute(parameter);80 }81 }82 }83 }
Command类是实现ICommand接口,从而实现将前台的命令注册到后台的ViewModel中,在此不详细展开讲解,请先照抄代码,如果对这部分感兴趣可以查阅博客园中关于ICommand接口的详解。暂时不理解这里并不影响后续的编码。
MainWindowViewModel.cs代码:
1 using System.Collections.ObjectModel;2 using System.ComponentModel;3 using System.Windows;4 using System.Windows.Controls;56 namespace WpfExample7 {8 public class MainWindowViewModel : INotifyPropertyChanged9 {10 //实现接口当数据源变动通知前台UI11 public event PropertyChangedEventHandler? PropertyChanged;1213 public void RaisePropertyChanged(string propName)14 {15 if (PropertyChanged != null)16 {17 PropertyChanged(this, new PropertyChangedEventArgs(propName));18 }19 }2021 /// <summary>22 /// 前台DataGrid绑定的People集合23 /// </summary>24 public ObservableCollection<Person> People { get; set; }2526 /// <summary>27 /// 绑定前台DataGrid控件SelectedItem字段上,用于保存当前选中的Item所对应的数据源28 /// </summary>29 public Person SelectItem30 {31 get32 {33 return m_SelectItem;34 }35 set36 {37 m_SelectItem = value;38 RaisePropertyChanged("SelectItem");39 }40 }4142 /// <summary>43 /// DataGrid控件中删除按钮命令44 /// </summary>45 public Command DelClick46 {47 get48 {49 if (m_DelClick == null)50 m_DelClick = new Command(DeleteEvent);5152 return m_DelClick;53 }54 }5556 /// <summary>57 /// 前台添加小刚按钮命令58 /// </summary>59 public Command AddClick60 {61 get62 {63 if (m_AddClick == null)64 m_AddClick = new Command(AdditionEvent);6566 return m_AddClick;67 }68 }6970 /// <summary>71 /// 前台修改Text按钮命令72 /// </summary>73 public Command ReviseClick74 {75 get76 {77 if (m_ReviseClick == null)78 m_ReviseClick = new Command(ReviseEvent);7980 return m_ReviseClick;81 }82 }8384 /// <summary>85 /// 前台TextBlock控件显示的文本86 /// </summary>87 public string TextInfo88 {89 get90 {91 return m_TextInfo;92 }93 set94 {95 m_TextInfo = value;96 //数据源更新调用更新前台UI方法97 RaisePropertyChanged("TextInfo");98 }99 }100101 /// <summary>102 /// DataGrid控件电话信息的TextBox键盘按下回车命令103 /// </summary>104 public Command PressEnterKey105 {106 get107 {108 if (m_PressEnterKey == null)109 m_PressEnterKey = new Command(PressEnterKeyEvent);110111 return m_PressEnterKey;112 }113 }114115 private Person m_SelectItem;116 private Command m_DelClick;117 private Command m_AddClick;118 private Command m_ReviseClick;119 private string m_TextInfo;120 private Command m_PressEnterKey;121122 /// <summary>123 /// 构造方法124 /// </summary>125 public MainWindowViewModel()126 {127 People = new ObservableCollection<Person>();128129 Person person1 = new Person() { Name = "小明", Age = 12, Sex = "男", Phone = "110" };130 Person person2 = new Person() { Name = "小红", Age = 13, Sex = "女", Phone = "119" };131 Person person3 = new Person() { Name = "小王", Age = 15, Sex = "男", Phone = "120" };132133 People.Add(person1);134 People.Add(person2);135 People.Add(person3);136137 TextInfo = "点击右侧按钮这里内容将会变化!";138 }139140 /// <summary>141 /// DataGrid控件中删除按钮事件142 /// </summary>143 /// <param name="obj">可传入前台控件</param>144 private void DeleteEvent(object obj)145 {146 if (MessageBox.Show($"是否删除{SelectItem.Name}的数据?", "提示", MessageBoxButton.YesNo) == MessageBoxResult.Yes)147 {148 People.Remove(SelectItem);149 }150 }151152 /// <summary>153 /// 前台添加小刚按钮事件154 /// </summary>155 /// <param name="obj">可传入前台控件</param>156 private void AdditionEvent(object obj)157 {158 if (MessageBox.Show("是否添加“姓名:小刚,年龄:18,性别:女,电话:123”?", "提示", MessageBoxButton.YesNo) == MessageBoxResult.Yes)159 {160 People.Add(new Person() { Name = "小刚", Age = 18, Sex = "女", Phone = "123" });161 }162 }163164 /// <summary>165 /// 前台修改Text按钮事件166 /// </summary>167 /// <param name="obj">可传入前台控件</param>168 private void ReviseEvent(object obj)169 {170 if (TextInfo == "点击右侧按钮这里内容将会变化!")171 {172 TextInfo = "点击了右侧按钮!!!!!!!!!";173 }174 else175 {176 TextInfo = "点击右侧按钮这里内容将会变化!";177 }178 }179180 /// <summary>181 /// DataGrid控件电话信息的TextBox键盘按下回车事件182 /// </summary>183 /// <param name="obj">可传入前台控件</param>184 private void PressEnterKeyEvent(object obj)185 {186 TextBox textBox = (TextBox)obj;187 MessageBox.Show($"点击了回车!控件内容为:{textBox.Text}");188 }189190 /// <summary>191 /// 数据结构192 /// </summary>193 public class Person194 {195 public string Name { get; set; }196 public int Age { get; set; }197 public string Sex { get; set; }198 public string Phone { get; set; }199 }200 }201 }
MainWindowsViewModel类中编写业务逻辑代码。
MainWindow.xaml代码:
<Window x:Class="WpfExample.MainWindow"2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6 xmlns:local="clr-namespace:WpfExample"7 mc:Ignorable="d"8 Title="MainWindow" Height="450" Width="800">9 <Grid>10 <DataGrid Margin="10,10,10,50" ItemsSource="{Binding People, Mode=TwoWay}" SelectedItem="{Binding SelectItem}" AutoGenerateColumns="False" CanUserAddRows="False">1112 <DataGrid.Columns>13 <DataGridTextColumn Header="姓名" Binding="{Binding Name}" Width="*" />14 <DataGridTextColumn Header="年龄" Binding="{Binding Age}" Width="*" />15 <DataGridTextColumn Header="性别" Binding="{Binding Sex}" Width="*" />16 <DataGridTemplateColumn Header="电话" Width="*">17 <DataGridTemplateColumn.CellTemplate>18 <DataTemplate>19 <TextBox x:Name="TextBox_Phone" Text="{Binding Phone}">20 <TextBox.InputBindings>21 <KeyBinding Key="Return" Command="{Binding Path=DataContext.PressEnterKey, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGrid}}" CommandParameter="{Binding ElementName=TextBox_Phone}"></KeyBinding>22 </TextBox.InputBindings>23 </TextBox>24 </DataTemplate>25 </DataGridTemplateColumn.CellTemplate>26 </DataGridTemplateColumn>27 <DataGridTemplateColumn Header="删除" Width="*">28 <DataGridTemplateColumn.CellTemplate>29 <DataTemplate>30 <Button Content="删除" Command="{Binding Path=DataContext.DelClick, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DataGrid}}" />31 </DataTemplate>32 </DataGridTemplateColumn.CellTemplate>33 </DataGridTemplateColumn>34 </DataGrid.Columns>35 </DataGrid>36 <Button Margin="10,0,10,10" VerticalAlignment="Bottom" HorizontalAlignment="Left" Height="30" Width="100" Content="添加学生小刚" Command="{Binding AddClick}"></Button>37 <TextBlock Margin="0,0,140,10" VerticalAlignment="Bottom" HorizontalAlignment="Right" Text="{Binding TextInfo}" FontSize="24"></TextBlock>38 <Button Margin="0,0,10,10" VerticalAlignment="Bottom" HorizontalAlignment="Right" Height="30" Width="100" Content="修改Text内容" Command="{Binding ReviseClick}"></Button>39 </Grid>40 </Window>
MainWindow.xaml.cs
1 using System.Windows;23 namespace WpfExample4 {5 public partial class MainWindow : Window6 {7 public MainWindow()8 {9 InitializeComponent();10 //绑定DataContext11 DataContext = new MainWindowViewModel();12 }13 }14 }
实例效果:
 
主界面窗口,支持点击删除按钮删掉对应信息;支持修改姓名,年龄,性别,电话信息;支持修改电话信息按下回车键捕捉回车事件功能;支持点击左下角添加学生小刚按钮增加信息;支持点击右下角修改Text内容下方TextBlok控件内容变化。


点击删除按钮,弹出确认删除对话框,点击是则删除成功。


点击左下角“添加学生小刚”按钮,弹出是否确认添加小刚信息确认框,点击是则会添加小刚的信息。

在电话信息中输入回车会显示点击了回车弹框,并且显示控件中的内容。

点击右下角“修改Text内容按钮”下方TextBlock控件内容发生改变

修改小明的姓名后点击删除按钮,提示信息显示小明的姓名也被更改,说明数据源同时进行了更改。
1.数据源:数据绑定是通过ViewModel作为数据源,绑定到前台xaml进行实现的。通过后台对于数据源的修改,可以将内容直接同步到前台界面上。可以详见上面数据的删除和添加以及修改Text的实例。People、SelectItem、TextInfo都是数据源。
2.命令:除了数据要进行实时更新以外,前台的操作也应该能够传输到后台,在后台逻辑做出响应。这时我们需要用到Command(命令),在本实例中展示了将按钮的点击事件和键盘的回车事件通过命令的方式传到后台,但命令的用法远不止这两种,可以在实际的开发过程中跟据不同的需求在进行学习和尝试。值得注意的是命令是可以带参数的,前台代码中的CommandParameter就是它的参数,例如按钮点击的命令可以通过参数来确定点击的是哪一个按钮,当然也可以给每一个按钮绑定一个独立的命令。DelClick、AddClick、ReviseClick、PressEnterKey都是命令。
3.数据模板的重写:在本实例中重写了DataGrid控件中的电话一列和删除一列的数据模板,我们可以看到电话一列重写为了TextBox删除一列重写为了Button,表头也可以进行数据模板的重写。在DataTemplate中你可以写几乎任意的控件,如果需要放多个控件可以使用Grid和StackPanel这类布局控件进行封装。值得注意的是当重写数据模板之后,对于命令绑定的写法需要格外注意,需要注意Path和RelativeSource属性,详见例子代码中的写法,如果按照常规Binding的写法你会发现后端无法收到你绑定的命令。
4.双向绑定:顾名思义绑定是双向的,不仅仅是后台数据更新后自动同步到前台,同时前台的数据更新也会自动同步到后台。这种双向绑定也是MVVM设计模式的一大特点,本实例中可以看到修改了小明的名字后,修改的内容在你没有进行任何操作的情况下自动同步到了后台的数据源中(值得注意的是这里需要让选中的cell失去焦点修改的内容才会同步到后台数据源),这就是双向绑定。当然在绑定的过程中你可以设置多种模式,如果不设置默认为双向绑定,设置的方法是通过Mode属性(Binding="{Binding Name ,Mode=TwoWay}")你可以设置Default、OneTime、OneWay、OneWayToSource、TwoWay。
以上就是本实例中涉及一些要点,这些内容是做WPF和C#开发的基本内容,希望他们能对你的学习和工作起到一些帮助,如果对于本文某些用法写法说法有任何的意见欢迎指正交流。谢谢。
