上一次写了一篇:在WPF中实现数据验证的自定义提示之后,一直觉得这个方案太过勉强,逻辑上也不怎么清晰。苦于前两周要做开题报告,所以一直没来得及做一个好好的研究,这几天终于找到时间来改进那个方案了。考虑到原来的方案将XMAL代码和.net后置代码混合使用,使得验证逻辑很是混乱,所以在这里我用纯XMAL标记来实现验证的呈现,而具体的验证逻辑还是放在了数据类型中。我在这儿提供了两个方案:一个是使用Validation中的ErrorTemplate错误模板来实现一个炫丽的提示框,另一个则是使用了一个Popup提示达到同样的目的。
预备: 仍然先来看看几个比较重要的类型:
1、AdornedElementPlaceholder 类:微软在帮助文档中很描述了这的作用---表示ControlTemplate 中使用的元素,用于指定修饰控件相对于 ControlTemplate 中的其他元素所放置的位置。
这里面我们要用到一个非常重要的属性:
AdornedElementPlaceholder . AdornedElement 属性:
获取此 AdornedElementPlaceholder 对象所引用的 UIElement。 我们要用这个属性来得到Validation附加属性的错误信息。
2、Validation 类:这是一个静态类,用于提供支持数据验证并管理控件的可视状态的方法和附加属性。
同样我们只关注它的附加属性:
Validation. Errors 附加属性:获取与绑定目标元素相关联的 ValidationError 对象的集合。
Validation. HasError 附加属性:获取一个值,该值指示目标元素的绑定是否具有验证错误。
这里有一个前提:那就是我们的数据类是实现了INotifyPropertyChanged,IDataErrorInfo 两个接口。
方案一:定义ErrorTemplate错误面板提示
1、首先定义一个提示面板:其效果如下
这里我们定义了一个TextBlock来绑定到AdornedElementPlaceholder的引用元素的Validation.Errors[0].ErrorContent属性上:
< TextBlock Foreground = " Red " x:Name = " errorText " Margin = " 15,0,2,2 " TextWrapping = " Wrap " FontSize = " 12 " HorizontalAlignment = " Left " VerticalAlignment = " Center " Text = " {Binding ElementName=adorner,Path=AdornedElement.(Validation.Errors)[0].ErrorContent} " />
框架的XAML标记为:
< Grid Height = " 47 " Width = " 175 " DockPanel.Dock = " Right " >
< Grid.Style >
< Style TargetType = " {x:Type Grid} " />
</ Grid.Style >
< Path Data = " M12.166999,0.5 L143.167,0.5 C145.92843,0.50000003 148.167,2.7385762 148.167,5.5 L148.167,30.5 C148.167,33.261425 145.92843,35.5 143.167,35.5 L12.166999,35.5 11.81918,35.482437 11.834001,35.5 C10.619751,36.16069 10.9795,36.5625 8.1912529,37.482075 5.4030061,38.401646 3.063751,40.272026 0.5,41.667 2.2780275,39.667053 4.7294998,37 5.8340837,35.66716 6.662521,34.66753 6.9629115,32.753006 7.1582331,31.348595 L7.1991105,31.052473 7.1928148,31.011221 C7.1757442,30.843136 7.1669998,30.672588 7.1669995,30.5 L7.1669995,17.25 7.1669995,5.5 C7.1669998,2.7385762 9.405576,0.50000003 12.166999,0.5 z " Margin = " 1.333,0,0,1.833 " Stretch = " Fill " Stroke = " #FF32E767 " UseLayoutRounding = " False " >
< Path.Fill >
< LinearGradientBrush EndPoint = " 0.5,1 " StartPoint = " 0.5,0 " >
< GradientStop Color = " #FF5F5DB6 " Offset = " 1 " />
< GradientStop Color = " #FF3F3BCA " />
< GradientStop Color = " #FFACABE9 " Offset = " 0.747 " />
</ LinearGradientBrush >
</ Path.Fill >
</ Path >
< Path Data = " M67.5,15.25 C70.750538,10.312179 121.33333,14.916667 148.25,14.75 148.41667,11.083333 149,6.0625 148.75,3.75 148.5,1.4375 145.43711,0.0625 144.5,0.25 149.46607,0.22253689 13.874996,-0.062023075 11.75,0.25 9.6250039,0.56202307 8.3747773,3.5006011 8.25,4.75 8.1252227,5.9993989 8.159157,10.23778 8.25,14.5 28,14.75 64.249462,20.187821 67.5,15.25 z " Margin = " 8,0,0,28.686 " Stretch = " Fill " Stroke = " Black " UseLayoutRounding = " False " StrokeThickness = " 0 " >
< Path.Fill >
< LinearGradientBrush EndPoint = " 0.5,1 " StartPoint = " 0.5,0 " >
< GradientStop Color = " #B2FFFFFF " />
< GradientStop Color = " #33FFFFFF " Offset = " 1 " />
</ LinearGradientBrush >
</ Path.Fill >
</ Path >
< TextBlock Foreground = " Red " x:Name = " errorText " Margin = " 15,0,2,2 " TextWrapping = " Wrap " FontSize = " 12 " HorizontalAlignment = " Left " VerticalAlignment = " Center " Text = " {Binding ElementName=adorner,Path=AdornedElement.(Validation.Errors)[0].ErrorContent} " />
</ Grid >
2、实现自定义的TextBox样式:
< Style x:Key = " ErrorValidationTextBoxStyle " BasedOn = " {x:Null} " TargetType = " {x:Type TextBox} " >
< Setter Property = " Foreground " Value = " {DynamicResource {x:Static SystemColors.ControlTextBrushKey}} " />
< Setter Property = " Background " Value = " {DynamicResource {x:Static SystemColors.WindowBrushKey}} " />
< Setter Property = " BorderBrush " Value = " {StaticResource TextBoxBorder} " />
< Setter Property = " BorderThickness " Value = " 1 " />
< Setter Property = " Padding " Value = " 1 " />
< Setter Property = " AllowDrop " Value = " true " />
< Setter Property = " FocusVisualStyle " Value = " {x:Null} " />
< Setter Property = " ScrollViewer.PanningMode " Value = " VerticalFirst " />
< Setter Property = " Stylus.IsFlicksEnabled " Value = " False " />
< Setter Property = " Template " >
< Setter.Value >
< ControlTemplate TargetType = " {x:Type TextBox} " >
< Microsoft_Windows_Themes:ListBoxChrome x:Name = " Bd " BorderBrush = " {TemplateBinding BorderBrush} " BorderThickness = " {TemplateBinding BorderThickness} " Background = " {TemplateBinding Background} " RenderMouseOver = " {TemplateBinding IsMouseOver} " RenderFocused = " {TemplateBinding IsKeyboardFocusWithin} " SnapsToDevicePixels = " true " >
< ScrollViewer x:Name = " PART_ContentHost " SnapsToDevicePixels = " {TemplateBinding SnapsToDevicePixels} " />
</ Microsoft_Windows_Themes:ListBoxChrome >
< ControlTemplate.Triggers >
< Trigger Property = " IsEnabled " Value = " false " >
< Setter Property = " Background " TargetName = " Bd " Value = " {DynamicResource {x:Static SystemColors.ControlBrushKey}} " />
< Setter Property = " Foreground " Value = " {DynamicResource {x:Static SystemColors.GrayTextBrushKey}} " />
</ Trigger >
</ ControlTemplate.Triggers >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
< \Style >
3、在上面的样式中加入如下的错误模板标记
< Setter Property = " Validation.ErrorTemplate " >
< Setter.Value >
< ControlTemplate >
< DockPanel LastChildFill = " True " >
<!-- ……这里放置上面的视觉树 -->
< Border BorderBrush = " DarkOrange " BorderThickness = " 1 " >
< AdornedElementPlaceholder x:Name = " adorner " />
</ Border >
</ DockPanel >
</ ControlTemplate >
</ Setter.Value >
</ Setter >
4、将样式附加到主窗体的TextBox中,并实现数据的绑定(这里的数据验证规则定义在相应的数据属性设置中):
< TextBox x:Name = " emailTxt " TextWrapping = " Wrap " Text = " {Binding Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, Path=Email} " VerticalAlignment = " Top " FontSize = " 18.667 " Height = " 44 " BorderBrush = " #FF1A3DA7 " Width = " 198 "
Style = " {DynamicResource ErrorValidationTextBoxStyle} " >
至此,第一方案已经完成:特别要提示的数据类要实现IDataErrorInfo接口定义的逻辑和正则表达式验证逻辑,其完整的代码请参考示例代码。
方案二:使用Popup控件的IsOpen依赖属性提示:
1、同样如一中的第一步定义一个提示面板。
2、将Popup的IsOpen绑定到文本框的Validation.HasError上,这是一个bool型的属性,所以我们可以直接绑定,不用自定义转换器了
< Popup x:Name = " phoneError " Placement = " Right " PlacementTarget = " {Binding ElementName=phoneTxt, Mode=OneWay} " AllowsTransparency = " True "
IsOpen = " {Binding ElementName=phoneTxt,Path=(Validation.HasError),Mode=OneWay} " >
3、在TextBox中实现与数据类的绑定连接:
< TextBox x:Name = " phoneTxt " TextWrapping = " Wrap " Text = " {Binding Phone, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True} " FontSize = " 18.667 " Margin = " 0,0,-98,0 " Height = " 44 " BorderBrush = " #FF1A3DA7 " Width = " 198 " HorizontalAlignment = " Left " >
最后不要忘记了在Binding在设置ValidatesOnDataErrors为True,这样才能实现数据出现错误的提示。由于Popup控件还定义了一组打开的动画效果,使用它可以得到很动感的提示体验,这是它的一个优势。而对于错误模板使用起来更方便,毕竟内部逻辑我们封装在了Style里,具体那一种方案要好些可就看个人的喜好了,我个人还是建议用错误模板要好些。毕竟有一种开之即可食的感觉。
点击下载 示例代码