当为一个集合(通常绑定在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. 发布两款JQ小插件(图片查看器 + 分类选择器),开源

    图片查看器,github地址:https://github.com/VaJoy/imgViewer 效果如下: 这款当初大概写了2小时,有点匆忙地赶出来的,使用的接口很简单: $.bindViewer ...

  2. 玩转Windows服务系列——命令行管理Windows服务

    说到Windows服务的管理就不得不说通过命令行的方式管理Windows服务,因为无论是系统管理员,还是通过编程的方式调用cmd命令,命令行都是非常方便以及强大的工具. 接下来就看一下如何通过cmd命 ...

  3. chunkupload 文件上传断点续传组件(java) - 正式发布

    chunkupload简介 chunkupload是一款基于java语言的断点续传组件,针对文件上传,非文件下载,集成方便,使用简单. chunkupload实现如下功能: ·  实现断点续传 ·  ...

  4. Html5绘制时钟

    最近在对Html5比较感兴趣,就用空闲时间做一些小例子进行练习,今天绘制一个走动的时钟,具体如下图所示: 具体思路在上图已有说明,代码如下: <script type="text/ja ...

  5. addUser

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. iOS-多线程基础

    进程与线程: 1>   一个应用程序对应一个进程,一个进程帮助程序占据一块存储空间 2>   要想在进程中执行任务,就必须开启线程,一条线程就代表一个任务 3>   一个进程中允许开 ...

  7. Red Gate(SQLToolbelt)SQL Server的安装与注册(破解)

    Red Gate(SQLToolbelt)是SQL Server辅佐工具 1.SQL Compare 比较和同步SQL Server数据库结构 2.SQL Data Compare 比较和同步SQL ...

  8. Distributed2:Linked Server Login 添加和删除

    一,通过 sys.sp_addlinkedsrvlogin 创建Linked Server的Login 当在local Server 上需要访问Linked Server时,Local Server ...

  9. DOM扩展-Selectors API(选择符 API)、元素遍历

    DOM扩展 对DOM的两个主要扩展是SelectorsAPI(选择符API)和HTML5 SelectorsAPI(选择符API)是由W3C发起制定的一个标准,致力于浏览器原生支持CSS查询,Sele ...

  10. AngularJs 动态加载模块和依赖

    最近项目比较忙额,白天要上班,晚上回来还需要做Angular知识点的ppt给同事,毕竟年底要辞职了,项目的后续开发还是需要有人接手的,所以就占用了晚上学习的时间.本来一直不打算写这些第三方插件的学习笔 ...