当为一个集合(通常绑定在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 取消编辑

FrameworkElementFrameworkContentElement 都有 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数据编辑的提交与撤销的更多相关文章

  1. git 在提交之前撤销add操作

    问题 在使用git时,在未添加.ignore文件前使用 git add . 将所有文件添加到库中,不小心将一些不需要加入版本库的文件加到了版本库中.由于此时还没有提交所以不存在HEAD版本,不能使用 ...

  2. 【C#/WPF】图像变换的Undo撤销——用Stack命令栈

    需求: 图层中有一张图片,可以对该图层进行平移.缩放.旋转操作,现在要求做Undo撤销功能,使得图层回复上一步操作时的状态. 关于图像的平移.缩放.旋转,可以参考在下的另一篇博客的整理: http:/ ...

  3. git- 仓库创建、修改、提交、撤销

    1.仓库创建 zhangshuli@zhangshuli-MS-:~$ mkdir myGit zhangshuli@zhangshuli-MS-:~$ cd myGit/ zhangshuli@zh ...

  4. 在WPF应用程序中利用IEditableObject接口实现可撤销编辑的对象

    这是我辅导的一个项目开发中的例子,他们是用WPF做界面开发,在学习了如何使用MVVM来实现界面与逻辑的分离,并且很好的数据更新之后,有一个疑问就是,这种双向的数据更新确实很不错,但如果我们希望用户可以 ...

  5. Git撤销对远程仓库的push&commit提交

    撤销push 1. 执行  git log查看日志,获取需要回退的版本号 2. 执行 git reset –soft <版本号> ,如 git reset -soft 4f5e9a90ed ...

  6. git push撤销、git commit 撤销、git add撤销、修改git提交信息

    原文地址:http://leisure.wang/?p=472 虽然自觉是一个Git工具的老手了,但是平时犯了一点错误,就发现有点捉襟见肘了.就好像今天我把一些代码玩坏了,想撤回到前几个版本去(此时已 ...

  7. oracle撤销表空间和回滚段

    /* 撤销表空间 */ 通过使用撤销技术,能够为Oracle数据库提供以下功能: * 使用ROLLBACK语句撤销事务 * 进行数据库恢复 * 提供数据的读一致性 Oracle强烈建议DBA在Orac ...

  8. git与eclipse集成之代码提交

    1.1. 代码提交 编码完成后,需要提交代码,例如新增文件git.txt 1.1.1.        提交代码到个人本地特性分支(commit) 选择工程,右键Team,Synchronize Wor ...

  9. Git-撤销(回退)已经add,commit或push的提交

    本文只阐述如何解决问题,不会对git的各种概念多做介绍,如果有兴趣可以点击下面的链接,进行详细的学习:Pro Git本文适用的环境 现在先假设几个环境,本文将会给出相应的解决方法:1. 本地代码(或文 ...

随机推荐

  1. 如何在Visual Studio 工程之间共享静态内容 (js, css, img, etc.)

     第一步: 文件夹上点击右键 -> Add -> Existing Item,单击选中文件,不要点击“Add”按钮,而是在“Add”按钮右边有个向下的小箭头,点击这个箭头,再点击“Add ...

  2. 剑指Offer面试题:15.反转链表

    一.题目:反转链表 题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点. 链表结点定义如下,这里使用的是C#描述: public class Node { public in ...

  3. Entity Framework Code First数据库连接

    1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器->程序包管理器控制台,执行以下语句: PM> Insta ...

  4. LeetCode Note 1st,practice makes perfect

    1. Two Sum Given an array of integers, return indices of the two numbers such that they add up to a ...

  5. Html5 布局方式

    在Html5之前,统一采用的是Div+css的方式进行布局,但是却和开发人员的命名方式,喜好有关.在新的Html5中,布局却显得更加人性化,更易理解了.如增加了Header,Footer,Sectio ...

  6. Win8换成Win7系统问题小结(修改主板BIOS方法)

    问题描述: 笔记本电脑W8系统使用不习惯,想要换成W7系统,但不管是用光盘安装亦或是用U盘安装,在设置系统启动项的时候,选择从光盘启动或从U盘启动,但是回车点了之后没反应. 下面就说说问题的原因及解决 ...

  7. 【hadoop摸索系列】记录使用libhdfs访问hdfs的关键问题

    hadoop官方的二进制发布版本一直是32位平台编译的,对于java来说跨平台不影响使用,但是为了在c/c++程序中操作hdfs就做不到了,因为libhdfs.so是二进制不兼容的. 我使用的是sta ...

  8. Caffe学习笔记2--Ubuntu 14.04 64bit 安装Caffe(GPU版本)

    0.检查配置 1. VMWare上运行的Ubuntu,并不能支持真实的GPU(除了特定版本的VMWare和特定的GPU,要求条件严格,所以我在VMWare上搭建好了Caffe环境后,又重新在Windo ...

  9. php的基础

    js是前段脚本语言 php是后端脚本语言 一.所建的文件都要存在wap下的www里面 二.所有的文件名都不能包含中文 三.通过输入 localhost/www下的文件名称,可以浏览 四.在DW内新建站 ...

  10. Android音频开发之AudioTrack实时播放

    前言: 其实在Android中录音可以用MediaRecord录音,操作比较简单.但是不能对音频进行处理.考虑到项目中做的是实时语音只能选择AudioRecord进行录音.然后实时播放也只能采用Aud ...