前文介绍了自定义或系统自带的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。  

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

<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所在区域与客服代表所在区域一致。
 
 
<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. \n ", customerName, region);
return new ValidationResult(false, sb.ToString());
}
}
} http://www.cnblogs.com/iwteih/archive/2012/04/10/2441576.html

最新文章

  1. [连载]《C#通讯(串口和网络)框架的设计与实现》-4.设备驱动管理器的设计
  2. MySQL, 创建一个只读用户和一个所有权限用户
  3. python - socket - server
  4. js之正则1
  5. Cobub Razor
  6. hasshmap输出value
  7. POJ2891 - Strange Way to Express Integers(模线性方程组)
  8. css(html)背景图优化合并
  9. Symmetric Tree——LeetCode
  10. 对获取config文件的appSettings节点简单封装
  11. HdU 4046 Panda 段树
  12. latch releae overview
  13. 游记-NOIP2018
  14. LOJ #10130 点的距离
  15. Spark MLlib 之 aggregate和treeAggregate从原理到应用
  16. (O)JS核心:call、apply和bind
  17. git如何获取用户名和邮箱
  18. spring boot application.properties/application.yml 配置属性大全
  19. Java awt组件间的继承关系
  20. 微信Web APP应用

热门文章

  1. 【前端学习笔记】2015-09-02~~~~ 关于filter()匹配的使用
  2. 【Windows API】OpenClipboard --- 剪切板(转)
  3. ES6 的Object.assign(target, source_1, &#183;&#183;&#183;)方法与对象的扩展运算符
  4. SQL盲注工具BBQSQL
  5. 透明代理Transparent Proxy
  6. DNA的分子结构
  7. .net 哈希
  8. 结构体和类中属性定义需要static地方
  9. 表现层 JSP 页面实现
  10. Opencv 最小外接矩形合并拼接