WPF数据编辑的提交与撤销
当为一个集合(通常绑定在DataGrid或其它ItemsControl控件)添加或编辑一个项时,通常会弹出一个编辑界面编辑项的属性,编辑结束再提交,或者我们不想编辑数据了,此时选择取消,数据项的内容没有任何改变。
在将数据项绑定到编辑界面时,我们可以定义绑定源更新的触发方式,如下代码所示,将TextBox的Text属性的绑定设置为 UpdateSourceTrigger="Explicit",此时需要手动触发数据源的更新。
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="Explicit" >
<Binding.ValidationRules>
<validateRule:AgeRangeRule Min="21" Max="130" ValidationStep="ConvertedProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
但上述方法的缺点是不会触发验证,这个缺点很要命,我们通常需要数据验证来可视化反馈输入错误,并提示用户输入规范的内容。
官方提供的解决方案是,使用BindingGroup及让数据类实现IEditableObject接口,实现该接口的方法以提供数据的提交、撤销和结束。下面的是一个数据类的定义,实际的数据由Empolyee类存储,它定义在EmployeeEditAgent的内部实现,EmployeeEditAgent相当于Empolyee的代理,它实现了IEditableObject接口,EmployeeEditAgent中定义了当前数据currentEmployee 和备份数据copyEmployee ,当调用BeginEdit方法时,currentEmployee复制给currentEmployee,在调用CancelEdit方法,currentEmployee 复制给currentEmployee,这里的复制是深层拷贝,通过备份和恢复的方式实现数据的撤销编辑。

public class EmployeeEditAgent : INotifyPropertyChanged, IEditableObject
{ [Serializable]
class Employee
{
internal string Name { get; set; }
internal int Age { get; set; }
internal float Salary { get; set; }
}
private Employee copyEmployee = null;
private Employee currentEmployee = new Employee(); public string Name
{
get { return currentEmployee.Name; }
set
{
if (currentEmployee.Name != value)
{
currentEmployee.Name = value;
RaisePropertyChanged("Name");
}
}
} public int Age
{
get { return currentEmployee.Age; }
set
{
if (currentEmployee.Age != value)
{
currentEmployee.Age = value;
RaisePropertyChanged("Age");
}
}
} public float Salary
{
get { return currentEmployee.Salary; }
set
{
if (currentEmployee.Salary != value)
{
currentEmployee.Salary = value;
RaisePropertyChanged("Salary");
}
}
} #region Implementation of INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged = delegate { }; public void RaisePropertyChanged(string propertyname)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
#endregion #region Implementation of IEditableObject public void BeginEdit()
{
copyEmployee = DeepColone(currentEmployee);
} public void EndEdit()
{
copyEmployee = null;
} public void CancelEdit()
{
currentEmployee = DeepColone(copyEmployee);
RaisePropertyChanged("");
} #endregion private T DeepColone<T>(T t)
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, t);
stream.Position = 0;
return (T)formatter.Deserialize(stream);
}
}
定义了数据类,还要了解BindingGroup类,BindingGroup可以同时更新多个绑定源,如果某个绑定验证不通过,则提交失败。调用BindingGroup的方法会相应调用绑定源实现的IEditableObject接口的方法:
BindingGroup | IEditableObject | |
BeginEdit | BeginEdit | 开始编辑 |
CommitEdit | EndEdit | 提交编辑 |
CancelEdit | CancelEdit | 取消编辑 |
FrameworkElement 或 FrameworkContentElement 都有 BindingGroup 属性,对于上面的定义个数据类,通常我们将其三个属性分别绑定到三个TextBox上,而三个控件通常位于同一个容器(StackPanel或Grid设置Window)内,此时将容器的DataContext设置为数据源,在容器上创建BindingGroup,此时三个TextBox会继承容器的BindingGroup,即当我们为任意一个TextBox设置绑定时,该绑定会添加到容器的BindingGroup中,这样便实现绑定的同时提交,下面是XAML的实现,注意代码注释说明:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:validateRule="clr-namespace:ValidateRule"
xmlns:wpfApplication2="clr-namespace:WpfApplication2"
Title="数据验证演示" Height="217" Width="332">
<Window.Resources>
<wpfApplication2:Employee Name="MJ" Age="25" Salary="2500" x:Key="employee"></wpfApplication2:Employee>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<AdornedElementPlaceholder/>
<TextBlock Foreground="Red" FontSize="20">!</TextBlock>
</DockPanel>
</ControlTemplate> <Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.BindingGroup>
<BindingGroup ></BindingGroup> <!--此处实例化了Window的BindingGroup属性-->
</Window.BindingGroup>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="118*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="29"/>
<RowDefinition Height="110"/>
<RowDefinition Height="100*"/>
</Grid.RowDefinitions>
<StackPanel Name="stackPanel" Grid.Row="1" Margin="5" Loaded="stackPanel_Loaded" >
<!--<StackPanel.BindingGroup>
<BindingGroup ></BindingGroup>
</StackPanel.BindingGroup>-->
<StackPanel Orientation="Horizontal" >
<Label Height="30">姓名</Label>
<TextBox Width="70" Text="{Binding Path=Name}"></TextBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Height="30">年龄</Label>
<TextBox Width="70" Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Age" UpdateSourceTrigger="PropertyChanged" >
<!--该绑定会添加到Window的BindingGroup-->
<Binding.ValidationRules>
<validateRule:AgeRangeRule Min="21" Max="130" ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text> </TextBox>
<TextBlock TextWrapping="Wrap" Text="{Binding Path=Age}" Width="98"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0">
<Label Height="30">工资</Label>
<TextBox Width="70" Text="{Binding Path=Salary}"></TextBox>
</StackPanel>
</StackPanel>
<Label Content="员工信息" FontSize="15"/>
<Button Content="提交" HorizontalAlignment="Left" Height="22" Margin="54,10,0,0" Grid.Row="2" VerticalAlignment="Top" Width="76" Click="Button_Click"/>
<Button Content="取消" HorizontalAlignment="Left" Height="22" Margin="165,10,0,0" Grid.Row="2" VerticalAlignment="Top" Width="76" Click="Button_Click_1"/>
</Grid>
</Window>
下面是代码的实现,注意BindingGroup三个方法BeginEdit、CommitEdit、CancelEdit的调用:

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// stackPanel.DataContext = new EmployeeEditAgent() { Name = "Mj", Age = 35, Salary = 2500 };
this.DataContext = new EmployeeEditAgent() { Name = "Mj", Age = 35, Salary = 2500 }; } private void stackPanel_Loaded(object sender, RoutedEventArgs e)
{
// stackPanel.BindingGroup.BeginEdit();
this.BindingGroup.BeginEdit();//开始编辑事务
} private void Button_Click(object sender, RoutedEventArgs e)
{
//if (stackPanel.BindingGroup.CommitEdit())
//{
// MessageBox.Show("success");
//}
//else
//{
// MessageBox.Show("");
//}
if (this.BindingGroup.CommitEdit())//提交编辑
{
MessageBox.Show("success");
}
else
{
MessageBox.Show("failed");
}
} private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.BindingGroup.CancelEdit();//取消编辑
}
}
WPF数据编辑的提交与撤销的更多相关文章
- git 在提交之前撤销add操作
问题 在使用git时,在未添加.ignore文件前使用 git add . 将所有文件添加到库中,不小心将一些不需要加入版本库的文件加到了版本库中.由于此时还没有提交所以不存在HEAD版本,不能使用 ...
- 【C#/WPF】图像变换的Undo撤销——用Stack命令栈
需求: 图层中有一张图片,可以对该图层进行平移.缩放.旋转操作,现在要求做Undo撤销功能,使得图层回复上一步操作时的状态. 关于图像的平移.缩放.旋转,可以参考在下的另一篇博客的整理: http:/ ...
- git- 仓库创建、修改、提交、撤销
1.仓库创建 zhangshuli@zhangshuli-MS-:~$ mkdir myGit zhangshuli@zhangshuli-MS-:~$ cd myGit/ zhangshuli@zh ...
- 在WPF应用程序中利用IEditableObject接口实现可撤销编辑的对象
这是我辅导的一个项目开发中的例子,他们是用WPF做界面开发,在学习了如何使用MVVM来实现界面与逻辑的分离,并且很好的数据更新之后,有一个疑问就是,这种双向的数据更新确实很不错,但如果我们希望用户可以 ...
- Git撤销对远程仓库的push&commit提交
撤销push 1. 执行 git log查看日志,获取需要回退的版本号 2. 执行 git reset –soft <版本号> ,如 git reset -soft 4f5e9a90ed ...
- git push撤销、git commit 撤销、git add撤销、修改git提交信息
原文地址:http://leisure.wang/?p=472 虽然自觉是一个Git工具的老手了,但是平时犯了一点错误,就发现有点捉襟见肘了.就好像今天我把一些代码玩坏了,想撤回到前几个版本去(此时已 ...
- oracle撤销表空间和回滚段
/* 撤销表空间 */ 通过使用撤销技术,能够为Oracle数据库提供以下功能: * 使用ROLLBACK语句撤销事务 * 进行数据库恢复 * 提供数据的读一致性 Oracle强烈建议DBA在Orac ...
- git与eclipse集成之代码提交
1.1. 代码提交 编码完成后,需要提交代码,例如新增文件git.txt 1.1.1. 提交代码到个人本地特性分支(commit) 选择工程,右键Team,Synchronize Wor ...
- Git-撤销(回退)已经add,commit或push的提交
本文只阐述如何解决问题,不会对git的各种概念多做介绍,如果有兴趣可以点击下面的链接,进行详细的学习:Pro Git本文适用的环境 现在先假设几个环境,本文将会给出相应的解决方法:1. 本地代码(或文 ...
随机推荐
- WebApiThrottle限流框架使用手册
阅读目录: 介绍 基于IP全局限流 基于IP的端点限流 基于IP和客户端key的端点限流 IP和客户端key的白名单 IP和客户端key自定义限制频率 端点自定义限制频率 关于被拒请求的计数器 在we ...
- 企业IT管理员IE11升级指南【3】—— IE11 新的GPO设置
企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...
- ASP.NET Web API路由系统:Web Host下的URL路由
ASP.NET Web API提供了一个独立于执行环境的抽象化的HTTP请求处理管道,而ASP.NET Web API自身的路由系统也不依赖于ASP.NET路由系统,所以它可以采用不同的寄宿方式运行于 ...
- C语言 · 出现次数最多的数
问题描述 编写一个程序,读入一组整数,这组整数是按照从小到大的顺序排列的,它们的个数N也是由用户输入的,最多不会超过20.然后程序将对这个数组进行统计,把出现次数最多的那个数组元素值打印出来.如果有两 ...
- 《Entity Framework 6 Recipes》中文翻译系列 (18) -----第三章 查询之结果集扁平化和多属性分组
翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 3-14 结果集扁平化 问题 你有一对多关联的两个实体,你想通过一个查询,获取关联 ...
- 启动 Apache2.2 的问题
启动 Apache2.2 的问题: windows不能在本地计算机启动 Apache2.2.有关更多信息,查阅系统事件日志.如果这是非Microsoft服务,请与服务厂商联系,并参考特定服务错误代码1 ...
- 生成模型(Generative Model)与判别模型(Discriminative Model)
摘要: 1.定义 2.常见算法 3.特性 4.优缺点 内容: 1.定义 1.1 生成模型: 在概率统计理论中, 生成模型是指能够随机生成观测数据的模型,尤其是在给定某些隐含参数的条件下.它给观测值和标 ...
- php语言
<?php//单行注释/*多行注释*///弱类型语言//var a=10;//php定义变量/*$a =10; //变量名前加$$b="hello";var_dump($a) ...
- 元素绝对定位以后设置了高宽,a标签不能点击的原因总结
元素绝对定位以后设置了高宽,a标签不能点击的原因: 1.元素内并无内容 2.背景是透明的,无任何背景图或者颜色 解决方法: 1.如果不是绝对定位元素的,可以用相对定位 2.给元素加透明的背景图 3.I ...
- Sql Server系列:自定义函数
用户自定义函数可以像系统函数一样在查询或存储过程中调用,可以接受参数.执行操作并将操作结果以值的形式返回.返回值可以是单个标量或结果集. 1. 标量函数 标量函数返回一个确定类型的标量值,对于多语句的 ...