你的位置:首页 > ASP.net教程

[ASP.net教程]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);    }  }

View Code

定义了数据类,还要了解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"    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    ="http://schemas.microsoft.com/winfx/2006/xaml"    ="clr-namespace:ValidateRule"    ="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();//取消编辑    }  }

View Code