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();//取消编辑 } }