zoukankan      html  css  js  c++  java
  • WPF3.5 使用BINDINGGROUP进行实体类和集合验证

    前文介绍了自定义或系统自带的ValidationRule进行验证,这种方法对于单个元素的验证不错。很多时候,我们需要对表单(Form)进行验证,也就是对一个实体类进行验证,或者对一个集合的每项进行验证,则显得不尽人意(每次只能验证一次)。WPF3.5中提供了BindingGroup用来验证多个绑定元素,可以对表单(form)和实体类进行验证。另外BindingGroup提供了Transcational的支持,就是说可以让操作回滚(BeginEdit,CancelEdit,CommitEdit)。BindingGroup的验证是同时进行的。可以设置BindingGroupName把一个Binding加入已存在的BindingGroup(就是BindingGroupName指定的)。

    MSDN上是这样说的:

    BindingGroup 在多个绑定之间创建关系,从而可一起验证和更新这些绑定。例如,假定某应用程序提示用户输入地址。然后该应用程序使用用户提供的值填充 Address 类型的对象,该对象具有 Street、City、ZipCode 和 Country 属性。该应用程序有一个包含四个 TextBox 控件的面板,其中每个控件均数据绑定到对象的属性之一。可以使用 BindingGroup 中的 ValidationRule 验证 Address 对象。如果绑定加入相同的 BindingGroup,则可以确保邮政编码对于地址所在国家/地区有效。

    设置 FrameworkElement 或 FrameworkContentElement 上的 BindingGroup 属性。正如任何其他可继承属性一样,子元素从其父元素继承 BindingGroup。如果发生以下情况之一,则会将子代元素上的绑定添加到 BindingGroup:

    在地址示例中,假定将 Panel 的 DataContext 设置为 Address 类型的对象。每个 TextBox 的绑定均添加到面板的 BindingGroup 中。

    将 ValidationRule 对象添加到 BindingGroup 中。在运行 ValidationRule 时,将 BindingGroup 作为 Validate 方法的第一个参数传递。可以使用该 BindingGroup 上的 TryGetValue 或 GetValue(Object, String) 方法获取对象的建议值,使用 Items 属性获取绑定的源。

    BindingGroup 在同一时间更新绑定的源,而不是分别更新每个绑定。在调用任一方法(ValidateWithoutUpdateUpdateSources 或 CommitEdit)验证数据时,将验证并可能会更新示例中的每个 TextBox 的绑定。当绑定是 BindingGroup 的一部分时,除非显式设置 UpdateSourceTrigger 属性,否则在对 BindingGroup 调用 UpdateSources 或 CommitEdit 之前,不会更新绑定的源。

    BindGroup常用成员:

    public class BindingGroup : DependencyObject
    {
      public Collection<BindingExpressionBase> BindingExpressions { get; }
      public bool CanRestoreValues { get; }
      public IList Items { get; }
      public string Name { get; set; }
      public bool NotifyOnValidationError { get; set; }
      public Collection<ValidationRule> ValidationRules { get; } 
    
      public void BeginEdit();
      public void CancelEdit();
      public bool CommitEdit();
      public object GetValue(object item, string propertyName);
      public bool TryGetValue(object item, string propertyName, out object value);
      public bool UpdateSources();
      public bool ValidateWithoutUpdate();
    }
     
    Items:BindingGroup 中的绑定对象所使用的源,是个List。所有作为源的对象都会被包含在Items中。通常,Items 中只有一项,即作为使用 BindingGroup 的元素的 DataContext 的对象。
    但是,BindingGroup 也可以包含多个源。例如,如果绑定对象共享同一 BindingGroupName 但使用不同的源对象,则用作源的每个对象均在 Items 中。
    如果绑定路径可解析为源的嵌套属性,则 Items 中也可有多个对象。例如,假定 TextBox 控件的绑定是 BindingGroup 的一部分,并且其 DataContext 是 Customer 对象,该对象具有 Address 类型的属性。
    如果 BindingPath 为 Address.ZipCode 属性,则 Address 会添加到 Items 属性中。
     
    NotifyOnValidationError:获取或设置在 ValidationRule 的状态更改时是否发生 Validation.Error 事件。
     
    BeginEdit:开始编辑事务。
     
    CommitEdit:运行所有的Rule,如果成功,则保存更改,更新源。
     
    CancelEdit:取消更改。
     
    以上三个,如果源对应的类继承自IEditableObject, 会调用IEditableObject中的相应方法。
     
    UpdateSources:运行所有ValidationStep设置为RawProposedValueConvertedProposedValueUpdatedValue的Rule。如果成功,更新源。此方法不会挂起事务并结束事务,也就是说调用完该方法后事务还是处于运行中。
     
    ValidateWithoutUpdate:如同UpdateSources,但是不会更新源。
     
    所以有三个方法可以用作验证:CommitEdit,UpdateSources,ValidateWithoutUpdate。  

    先看看验证实体类的示例:

    image

    <Window x:Class="ValidateItemSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:ValidateItemSample"
        Title="Validating an Object" Width="400" Height="500" ResizeMode="NoResize">
      
      <StackPanel Name="stackPanel1"  Margin="10" 
                  Loaded="stackPanel1_Loaded"
                  Validation.Error="ItemError"><!--验证的错误在ItemError中处理,要求NotifyOnValidationError="True"--> 
    
        <StackPanel.Resources>
          <Style TargetType="HeaderedContentControl">
            <Setter Property="Margin" Value="2"/>
            <Setter Property="Focusable" Value="False"/>
            <Setter Property="Template">
              <Setter.Value>
                <ControlTemplate TargetType="HeaderedContentControl">
                  <DockPanel LastChildFill="False">
                    <ContentPresenter ContentSource="Header" DockPanel.Dock="Left" Focusable="False" VerticalAlignment="Center"/>
                    <ContentPresenter ContentSource="Content" Margin="5,0,0,0" DockPanel.Dock="Right" VerticalAlignment="Center"/>
                  </DockPanel>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
    
          <Style TargetType="Button">
            <Setter Property="Width" Value="100"/>
            <Setter Property="Margin" Value="10,15,15,15"/>
          </Style>
        </StackPanel.Resources>
    
        <!--BindingGroup-->
        <StackPanel.BindingGroup>
          <BindingGroup NotifyOnValidationError="True">
            <BindingGroup.ValidationRules>
              <src:ValidateDateAndPrice ValidationStep="ConvertedProposedValue" />
            </BindingGroup.ValidationRules>
          </BindingGroup>
        </StackPanel.BindingGroup>
    
        <TextBlock FontSize="12" TextWrapping="Wrap" Margin="5">
          This sample demonstrates how to validate an object by checking 
          multiple properties in a ValidationRule.  When a ValidationRule 
          is added to a BindingGroup, the rule can get the properties of
          the source item in the Validate method.
          <LineBreak/><LineBreak/>
          This sample checks that if an item costs more than 100 dollars, 
          the item is available for at least 7 days.
        </TextBlock>
        
        <TextBlock FontSize="14" FontWeight="Bold"
                   Text="Enter an item for sale"/>
        
        <HeaderedContentControl Header="Description">
          <TextBox Width="150" Text="{Binding Path=Description, Mode=TwoWay}"/>
        </HeaderedContentControl>
        <HeaderedContentControl Header="Price">
          <TextBox Name="priceField"  Width="150">
            <TextBox.Text>
              <Binding Path="Price" Mode="TwoWay" >
                <!--自定义的ValidationRule-->
                <Binding.ValidationRules>
                  <src:PriceIsAPositiveNumber/>
                </Binding.ValidationRules>
              </Binding>
            </TextBox.Text>
          </TextBox>
        </HeaderedContentControl>
        <HeaderedContentControl Header="Date Offer Ends">
          <TextBox Name="dateField" Width="150" >
            <TextBox.Text>
              <Binding Path="OfferExpires" StringFormat="d" >
                <!--自定义的ValidationRule--> 
                <Binding.ValidationRules>
                  <src:FutureDateRule/>
                </Binding.ValidationRules>
              </Binding>
            </TextBox.Text>
          </TextBox>
        </HeaderedContentControl>
        <StackPanel Orientation="Horizontal">
          <Button IsDefault="True" Click="Submit_Click">_Submit</Button>
          <Button IsCancel="True" Click="Cancel_Click">_Cancel</Button>
        </StackPanel>
        <HeaderedContentControl Header="Description">
          <TextBlock Width="150" Text="{Binding Path=Description}"/>
        </HeaderedContentControl>
        <HeaderedContentControl Header="Price">
          <TextBlock Width="150" Text="{Binding Path=Price, StringFormat=c}"/>
        </HeaderedContentControl>
        <HeaderedContentControl Header="Date Offer Ends">
          <TextBlock Width="150" Text="{Binding Path=OfferExpires, StringFormat=d}"/>
        </HeaderedContentControl>
      </StackPanel>
    </Window>
    
     
            void stackPanel1_Loaded(object sender, RoutedEventArgs e)
            {
                // Set the DataContext to a PurchaseItem object.
                // The BindingGroup and Binding objects use this as
                // the source.
                stackPanel1.DataContext = new PurchaseItem();
    
                // Begin an edit transaction that enables
                // the object to accept or roll back changes.
                stackPanel1.BindingGroup.BeginEdit();
            }
    
            private void Submit_Click(object sender, RoutedEventArgs e)
            {
                //验证并提交
                if (stackPanel1.BindingGroup.CommitEdit())
                {
                    MessageBox.Show("Item submitted");
                    //提交成功后继续接收edit信息
                    stackPanel1.BindingGroup.BeginEdit();
                }
            }
    
    
            private void Cancel_Click(object sender, RoutedEventArgs e)
            {
                // Cancel the pending changes and begin a new edit transaction.
                stackPanel1.BindingGroup.CancelEdit();
                stackPanel1.BindingGroup.BeginEdit();
            }
    
            // This event occurs when a ValidationRule in the BindingGroup
            // or in a Binding fails.
            private void ItemError(object sender, ValidationErrorEventArgs e)
            {
                if (e.Action == ValidationErrorEventAction.Added)//描述是添加还是清除了 ValidationError 对象
                {
                    MessageBox.Show(e.Error.ErrorContent.ToString());
                }
            }

          ValidationRule文件:

        public class ValidateDateAndPrice : ValidationRule
        {
            // Ensure that an item over $100 is available for at least 7 days.
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                BindingGroup bg = value as BindingGroup;
    
                // Get the source object.
                PurchaseItem item = bg.Items[0] as PurchaseItem;
                
                object doubleValue;
                object dateTimeValue;
    
                // Get the proposed values for Price and OfferExpires.
                bool priceResult = bg.TryGetValue(item, "Price", out doubleValue);
                bool dateResult = bg.TryGetValue(item, "OfferExpires", out dateTimeValue);
    
                if (!priceResult || !dateResult)
                {
                    return new ValidationResult(false, "Properties not found");
                }
    
                double price = (double)doubleValue;
                DateTime offerExpires = (DateTime)dateTimeValue;
    
                // Check that an item over $100 is available for at least 7 days.
                if (price > 100)
                {
                    if (offerExpires < DateTime.Today + new TimeSpan(7, 0, 0, 0))
                    {
                        return new ValidationResult(false, "Items over $100 must be available for at least 7 days.");
                    }
                }
    
                return ValidationResult.ValidResult;
            }
        }
        
    
        //Ensure that the price is positive.
        public class PriceIsAPositiveNumber : ValidationRule
        {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                try
                {
                    double price = Convert.ToDouble(value);
    
                    if (price < 0)
                    {
                        return new ValidationResult(false, "Price must be positive.");
                    }
                    else
                    {
                        return ValidationResult.ValidResult;
                    }
                }
                catch (Exception)
                {
                    // Exception thrown by Conversion - value is not a number.
                    return new ValidationResult(false, "Price must be a number.");
                }
            }
        }
    
        // Ensure that the date is in the future.
        class FutureDateRule : ValidationRule
        {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
    
                DateTime date;
                try
                {
                    date = DateTime.Parse(value.ToString());
                }
                catch (FormatException)
                {
                    return new ValidationResult(false, "Value is not a valid date.");
                }
                if (DateTime.Now.Date > date)
                {
                    return new ValidationResult(false, "Please enter a date in the future.");
                }
                else
                {
                    return ValidationResult.ValidResult;
                }
            }
        }
    
        // PurchaseItem implements INotifyPropertyChanged and IEditableObject
        // to support edit transactions, which enable users to cancel pending changes.
        public class PurchaseItem : INotifyPropertyChanged, IEditableObject
        {
            struct ItemData
            {
                internal string Description;
                internal double Price;
                internal DateTime OfferExpires;
    
                static internal ItemData NewItem()
                {
                    ItemData data = new ItemData();
                    data.Description = "New item";
                    data.Price = 0;
                    data.OfferExpires = DateTime.Now + new TimeSpan(7, 0, 0, 0);
    
                    return data;
                }
            }
            ItemData copyData = ItemData.NewItem();
            ItemData currentData = ItemData.NewItem();
    
            public PurchaseItem()
            {
    
            }
    
            public PurchaseItem(string desc, double price, DateTime endDate)
            {
                Description = desc;
                Price = price;
                OfferExpires = endDate;
            }
    
            public override string ToString()
            {
                return String.Format("{0}, {1:c}, {2:D}", Description, Price, OfferExpires);
            }
    
            public string Description
            {
                get { return currentData.Description; }
                set
                {
                    if (currentData.Description != value)
                    {
                        currentData.Description = value;
                        NotifyPropertyChanged("Description");
                    }
                }
            }
    
            public double Price
            {
                get { return currentData.Price; }
                set
                {
                    if (currentData.Price != value)
                    {
                        currentData.Price = value;
                        NotifyPropertyChanged("Price");
                    }
                }
            }
    
            public DateTime OfferExpires
            {
                get { return currentData.OfferExpires; }
                set
                {
                    if (value != currentData.OfferExpires)
                    {
                        currentData.OfferExpires = value;
                        NotifyPropertyChanged("OfferExpires");
                    }
                }
            }
    
            #region INotifyPropertyChanged Members
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void NotifyPropertyChanged(String info)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(info));
                }
            }
            #endregion
    
            #region IEditableObject Members
            public void BeginEdit()
            {
                copyData = currentData;
            }
    
            public void CancelEdit()
            {
                currentData = copyData;
                NotifyPropertyChanged("");
    
            }
    
            public void EndEdit()
            {
                copyData = ItemData.NewItem();
    
            }
            #endregion
        }
    
    此例中PurchaseItem继承了IEditableObject,那么BindingGroup使用的BeginEdit,CancelEdit, EndEdit会使用IEditableObject中的相应方法。
     

    对集合的验证:

    下例点击Add Customer时,验证通过后会在集合中增加一个Customer对象,要求Customer所在区域与客服代表所在区域一致。
     
    image 
    <Window x:Class="ValidateItemInItemsControlSample.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=mscorlib" 
        xmlns:src="clr-namespace:ValidateItemInItemsControlSample"
        xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
        Title="Window1">
      <StackPanel>
        <StackPanel.Resources>
          <!--枚举值做数据源,使用ObjectDataProvider,这里有介绍--> 
          <!--方法原型是Enum.GetValues(Type),返回值是一数组--> 
          <ObjectDataProvider MethodName="GetValues"
                              ObjectType="{x:Type sys:Enum}"
                              x:Key="RegionValues">        
            <ObjectDataProvider.MethodParameters>
              <x:Type TypeName="src:Region" />
            </ObjectDataProvider.MethodParameters>
          </ObjectDataProvider>
    
          <!—Representantives是ServiceRep(客服代表)实例的集合--> 
          <src:Representantives x:Key="SaleReps"/>
    
          <!—集合中各项的模版--> 
          <DataTemplate x:Key="ItemTemplate" >
            <StackPanel Orientation="Horizontal" >
              <TextBlock Text="Customer Name" Margin="5"/>
              <TextBox Width="100" Margin="5" Text="{Binding Name}"/>
              <TextBlock Text="Region" Margin="5"/>
              <ComboBox ItemsSource="{Binding Source={StaticResource RegionValues}}" 
                        SelectedItem="{Binding Location}"  Width="100" Margin="5"/>
              <TextBlock Text="Service Representative" Margin="5"/>
              <ComboBox ItemsSource="{Binding Source={StaticResource SaleReps}}"
                        SelectedItem="{Binding ServiceRepresentative}"  Width="200" Margin="5"/>
              <Button Content="Save Customer" Click="saveCustomer_Click"/>          
            </StackPanel>
          </DataTemplate>
        </StackPanel.Resources>
    
        <TextBlock FontSize="14" TextWrapping="Wrap" Margin="5">
          This sample demonstrates how to validate an object in an ItemsControl.
          The ValidationRule assigned to ItemsControl.ItemBindingGroup checks 
          multiple properties in the item. 
          This sample checks that a customer is assigned to a sales representative that serves their area.      
        </TextBlock>
    
        <!—设置Itemtemplate和ItemSource--> 

    //注意ItemsControl的使用,在界面上的显示效果
        <ItemsControl Margin="5"  Name="customerList"  ItemTemplate="{StaticResource ItemTemplate}"
                      ItemsSource="{Binding}">
          <ItemsControl.ItemBindingGroup>
            <BindingGroup>
              <BindingGroup.ValidationRules>
                <src:AreasMatch/>
              </BindingGroup.ValidationRules>
            </BindingGroup>
          </ItemsControl.ItemBindingGroup>
          <!—获取或设置 Style,它应用于为每个项生成的容器元素。这是一个依赖项属性--> 
          <ItemsControl.ItemContainerStyle>
            <!—ItemsControl里的每项实际是以ContentPresenter作为UI显示的载体--> 
            <Style TargetType="{x:Type ContentPresenter}">
              <Setter Property="Validation.ValidationAdornerSite"
                                                    Value="{Binding ElementName=validationErrorReport}"/>
            </Style>
          </ItemsControl.ItemContainerStyle>
        </ItemsControl>
        <Label Name="validationErrorReport" 
                 Content="{Binding RelativeSource={RelativeSource Self}, 
               Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)[0].ErrorContent}"
               Margin="5" Foreground="Red" HorizontalAlignment="Center"/>
    
        <Button Content="Add Customer" Click="AddCustomer_Click" HorizontalAlignment="Center"/>
      </StackPanel>
    </Window>
    
    这里用了一个Label(validationErrorReport)来显示验证错误信息,验证的错误是以Validation.Errors这个Attached Property作为载体。

    通过Validation.ValidationAdornerSite和Validation.ValidationAdornerSiteFor可以设置错误消息源(ItemsControl中的各项)和接收错误的载体(Label)。
    但是这种做法是有点问题的,我在另一篇中会讲这个例子的运行效果。其有问题的原因是因为Validation类是静态类,里面的所有成员及方法都是静态的,只能对一个有效。

    Backend code:

        public partial class Window1 : Window
        {
            Customers customerData;
            BindingGroup bindingGroupInError = null;
    
            public Window1()
            {
                InitializeComponent();
    
                customerData = new Customers();
                // 设置ItemsControl的源
                customerList.DataContext = customerData;
            }
    
            void AddCustomer_Click(object sender, RoutedEventArgs e)
            {
                if (bindingGroupInError == null)
                {
                    customerData.Add(new Customer());
                }
                else
                {
                    MessageBox.Show("Please correct the data in error before adding a new customer.");
                }
            }
    
            void saveCustomer_Click(object sender, RoutedEventArgs e)
            {
                Button btn = sender as Button;
                // ItemsControl.ContainerFromElement MSND上是这么说的:返回属于拥有给定元素的当前 ItemsControl 的容器。读起来和念易筋经一样
    FrameworkElement container = (FrameworkElement) customerList.ContainerFromElement(btn); // If the user is trying to change an items, when another item has an error, // display a message and cancel the currently edited item. if (bindingGroupInError != null && bindingGroupInError != container.BindingGroup) { MessageBox.Show("Please correct the data in error before changing another customer"); container.BindingGroup.CancelEdit(); return; } if (container.BindingGroup.ValidateWithoutUpdate()) { container.BindingGroup.UpdateSources(); bindingGroupInError = null; MessageBox.Show("Item Saved"); } else { bindingGroupInError = container.BindingGroup; } }
     
    ValicationRule文件:
        public class Customers : ObservableCollection<Customer>
        {
            public Customers()
            {
                Add(new Customer());
            }
        }
    
        public enum Region
        {
            Africa,
            Antartica,
            Australia,
            Asia,
            Europe,
            NorthAmerica,
            SouthAmerica
        }
    
        public class Customer
        {
            public string Name { get; set; }
            public ServiceRep ServiceRepresentative { get; set; }
            public Region Location { get; set; }
        }
    
        public class ServiceRep
        {
            public string Name { get; set; }
            public Region Area { get; set; }
    
            public ServiceRep()
            {
            }
    
            public ServiceRep(string name, Region area)
            {
                Name = name;
                Area = area;
            }
    
            public override string ToString()
            {
                return Name + " - " + Area.ToString();
            }
        }
    
        public class Representantives : ObservableCollection<ServiceRep>
        {
            public Representantives()
            {
                Add(new ServiceRep("Haluk Kocak", Region.Africa));
                Add(new ServiceRep("Reed Koch", Region.Antartica));
                Add(new ServiceRep("Christine Koch", Region.Asia));
                Add(new ServiceRep("Alisa Lawyer", Region.Australia));
                Add(new ServiceRep("Petr Lazecky", Region.Europe));
                Add(new ServiceRep("Karina Leal", Region.NorthAmerica));
                Add(new ServiceRep("Kelley LeBeau", Region.SouthAmerica));
                Add(new ServiceRep("Yoichiro Okada", Region.Africa));
                Add(new ServiceRep("T¨¹lin Oktay", Region.Antartica));
                Add(new ServiceRep("Preeda Ola", Region.Asia));
                Add(new ServiceRep("Carole Poland", Region.Australia));
                Add(new ServiceRep("Idan Plonsky", Region.Europe));
                Add(new ServiceRep("Josh Pollock", Region.NorthAmerica));
                Add(new ServiceRep("Daphna Porath", Region.SouthAmerica));
            }
        }
    
        // Check whether the customer and service representative are in the
        // same area.
        public class AreasMatch : ValidationRule
        {
            public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
            {
                BindingGroup bg = value as BindingGroup;
                Customer cust = bg.Items[0] as Customer;
    
                if (cust == null)
                {
                    return new ValidationResult(false, "Customer is not the source object");
                }
    
                Region region = (Region)bg.GetValue(cust, "Location");
                ServiceRep rep = bg.GetValue(cust, "ServiceRepresentative") as ServiceRep;
                string customerName = bg.GetValue(cust, "Name") as string;
    
                // 相等说明验证通过 
                if (region == rep.Area)
                {
                    return ValidationResult.ValidResult;
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    sb.AppendFormat("{0} must be assigned a sales representative that serves the {1} region. 
     ", customerName, region);
                    return new ValidationResult(false, sb.ToString());
                }
            }
        }

    http://www.cnblogs.com/iwteih/archive/2012/04/10/2441576.html
  • 相关阅读:
    Day4
    Day 4 -E
    Day4
    Day4
    Day4
    Day4
    Day4-F-产生冠军 HDU
    Day4
    Day4-B-最短路径问题 HDU3790
    HackerRank
  • 原文地址:https://www.cnblogs.com/sjqq/p/7841292.html
Copyright © 2011-2022 走看看