写在前面
本文将会介绍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;
3
4 namespace WpfExample
5 {
6 public class Command : ICommand
7 {
8
9 /// <summary>
10 /// 检查命令是否可以执行的事件,在UI事件发生导致控件状态或数据发生变化时触发
11 /// </summary>
12 public event EventHandler CanExecuteChanged
13 {
14 add
15 {
16 if (_canExecute != null)
17 {
18 CommandManager.RequerySuggested += value;
19 }
20 }
21 remove
22 {
23 if (_canExecute != null)
24 {
25 CommandManager.RequerySuggested -= value;
26 }
27 }
28 }
29
30 /// <summary>
31 /// 判断命令是否可以执行的方法
32 /// </summary>
33 private Func<object, bool> _canExecute;
34
35 /// <summary>
36 /// 命令需要执行的方法
37 /// </summary>
38 private Action<object> _execute;
39
40 /// <summary>
41 /// 创建一个命令
42 /// </summary>
43 /// <param name="execute">命令要执行的方法</param>
44 public Command(Action<object> execute) : this(execute, null)
45 {
46
47 }
48
49 /// <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 }
59
60 /// <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 }
70
71 /// <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.cs
Command类是实现ICommand接口,从而实现将前台的命令注册到后台的ViewModel中,在此不详细展开讲解,请先照抄代码,如果对这部分感兴趣可以查阅博客园中关于ICommand接口的详解。暂时不理解这里并不影响后续的编码。
MainWindowViewModel.cs代码:
1 using System.Collections.ObjectModel;
2 using System.ComponentModel;
3 using System.Windows;
4 using System.Windows.Controls;
5
6 namespace WpfExample
7 {
8 public class MainWindowViewModel : INotifyPropertyChanged
9 {
10 //实现接口当数据源变动通知前台UI
11 public event PropertyChangedEventHandler? PropertyChanged;
12
13 public void RaisePropertyChanged(string propName)
14 {
15 if (PropertyChanged != null)
16 {
17 PropertyChanged(this, new PropertyChangedEventArgs(propName));
18 }
19 }
20
21 /// <summary>
22 /// 前台DataGrid绑定的People集合
23 /// </summary>
24 public ObservableCollection<Person> People { get; set; }
25
26 /// <summary>
27 /// 绑定前台DataGrid控件SelectedItem字段上,用于保存当前选中的Item所对应的数据源
28 /// </summary>
29 public Person SelectItem
30 {
31 get
32 {
33 return m_SelectItem;
34 }
35 set
36 {
37 m_SelectItem = value;
38 RaisePropertyChanged("SelectItem");
39 }
40 }
41
42 /// <summary>
43 /// DataGrid控件中删除按钮命令
44 /// </summary>
45 public Command DelClick
46 {
47 get
48 {
49 if (m_DelClick == null)
50 m_DelClick = new Command(DeleteEvent);
51
52 return m_DelClick;
53 }
54 }
55
56 /// <summary>
57 /// 前台添加小刚按钮命令
58 /// </summary>
59 public Command AddClick
60 {
61 get
62 {
63 if (m_AddClick == null)
64 m_AddClick = new Command(AdditionEvent);
65
66 return m_AddClick;
67 }
68 }
69
70 /// <summary>
71 /// 前台修改Text按钮命令
72 /// </summary>
73 public Command ReviseClick
74 {
75 get
76 {
77 if (m_ReviseClick == null)
78 m_ReviseClick = new Command(ReviseEvent);
79
80 return m_ReviseClick;
81 }
82 }
83
84 /// <summary>
85 /// 前台TextBlock控件显示的文本
86 /// </summary>
87 public string TextInfo
88 {
89 get
90 {
91 return m_TextInfo;
92 }
93 set
94 {
95 m_TextInfo = value;
96 //数据源更新调用更新前台UI方法
97 RaisePropertyChanged("TextInfo");
98 }
99 }
100
101 /// <summary>
102 /// DataGrid控件电话信息的TextBox键盘按下回车命令
103 /// </summary>
104 public Command PressEnterKey
105 {
106 get
107 {
108 if (m_PressEnterKey == null)
109 m_PressEnterKey = new Command(PressEnterKeyEvent);
110
111 return m_PressEnterKey;
112 }
113 }
114
115 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;
121
122 /// <summary>
123 /// 构造方法
124 /// </summary>
125 public MainWindowViewModel()
126 {
127 People = new ObservableCollection<Person>();
128
129 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" };
132
133 People.Add(person1);
134 People.Add(person2);
135 People.Add(person3);
136
137 TextInfo = "点击右侧按钮这里内容将会变化!";
138 }
139
140 /// <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 }
151
152 /// <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 }
163
164 /// <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 else
175 {
176 TextInfo = "点击右侧按钮这里内容将会变化!";
177 }
178 }
179
180 /// <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 }
189
190 /// <summary>
191 /// 数据结构
192 /// </summary>
193 public class Person
194 {
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 }
MainWindowViewModel.cs
MainWindowsViewModel类中编写业务逻辑代码。
MainWindow.xaml代码:
1 <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">
11
12 <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
MainWindow.xaml.cs
1 using System.Windows;
2
3 namespace WpfExample
4 {
5 public partial class MainWindow : Window
6 {
7 public MainWindow()
8 {
9 InitializeComponent();
10 //绑定DataContext
11 DataContext = new MainWindowViewModel();
12 }
13 }
14 }
MainWindow.xaml.cs
实例效果:

主界面窗口,支持点击删除按钮删掉对应信息;支持修改姓名,年龄,性别,电话信息;支持修改电话信息按下回车键捕捉回车事件功能;支持点击左下角添加学生小刚按钮增加信息;支持点击右下角修改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#开发的基本内容,希望他们能对你的学习和工作起到一些帮助,如果对于本文某些用法写法说法有任何的意见欢迎指正交流。谢谢。
2023.4.17下午两点五十五分
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这