虽然标题是wpf数据验证,但并不是对IDataErrorInfo、ValidationRule、属性中throw Exception这几种验证方式的介绍;
之前做项目时(例如员工工资管理),根据员工编号和年度月份验证 当月数据的唯一性,因为第一次开发wpf经验不足,所以用过几种不同的方式,并且现在用的这种方式也不是很满意,如果哪位大神有更好的办法 麻烦发个链接。
文章最后会把验证、列表的样式和验证中常使用的一些方法贴出来,方便大家使用;
列表页面 员工编号和年度月份验证
添加修改页面 填写编号选择月份后,验证不通过都是在编号处提示
一、言归正传,逐步写一下我当时的思路
1、为了实现这种需求的验证,最先想到的就是实现了ValidationRule的自定义验证类(ValidateExistLogName)能有一个属性(ValiByProperty) binding上月份中选择的值,关联月份和当前输入的员工编号来验证当月是否存在;
1
2
3
4
5
|
<Binding.ValidationRules> <tool:ValidateExistLogName ValiByProperty= "{Binding CurMonth}" /> </Binding.ValidationRules> |
但是只有DependencyObject派生类的DependencyProperty属性才能进行binding,于是我找到了给ValidationRule派生类的属性上binding的办法
参考链接:http://www.codeproject.com/Articles/18678/Attaching-a-Virtual-Branch-to-the-Logical-Tree-in
https://social.msdn.microsoft.com/Forums/vstudio/en-US/982e2fcf-780f-4f1c-9730-cedcd4e24320/binding-validationrules-property?forum=wpf
这种方式可能添加页面比较好实现,但是对于列表DataGrid恐怕binding起来就,也许有人说可以DataGrid的IsReadOnly=false,但是我的需求是修改页面修改的同时支持列表直接修改。
2、对实体类添加PropertyChangedEventHandler事件,这种方式可以实现,但是却不是在ValidationRule中验证,而且事件中的逻辑代码也稍较麻烦,因为e.PropertyName绑定的是datepicker控件时,需throw new Exception才能显示出来错误
1
2
3
4
5
6
7
8
9
10
|
列表中 初始化列表时遍历datagrid中的绑定源数据: foreach ( var item in data) { //为新加数据也加入事件 item.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(source_PropertyChanged); item.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(source_PropertyChanged); } 添加或者修改直接给绑定的实体类添加事件: source.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(source_PropertyChanged); |
3、期间还尝试了别的,但改动不大 基本记不清楚了,最后还是在派生自ValidationRule的类中添加需要验证的实体属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
public class ValidateExistCurMonthOrLogName : ValidationRule { public object Entity { get ; set ; } public int ValiPropertyType { get ; set ; } //1验证LogName,2验证CurMonth public override ValidationResult Validate( object value, CultureInfo cultureInfo) { if ( this .ValiPropertyType==1) { int tmp; if ( string .IsNullOrEmpty(value as string ) || string .IsNullOrWhiteSpace(value as string )) { return new ValidationResult( false , "不能为空!" ); } else if (! int .TryParse(value as string , out tmp)) { return new ValidationResult( false , "请输入正确的数字!" ); } else if (...验证是否已存在) ......... } ......... } } 1、DataGrid列表 //在单元格开始编辑的时候,把要验证的实体赋值 dataGrid.PreparingCellForEdit += delegate ( object sender, DataGridPreparingCellForEditEventArgs e) { //记录原始状态 AllowanceData model = e.Row.Item as AllowanceData; allowanceDataHelper.SourceToModel(model, originalModel); //获取cell DataGridCell cell = OperateControlHelper.GetCell(dataGrid, e.Row.GetIndex(), e.Column.DisplayIndex);<br> //判断当前编辑的是TextBox还是DatePicker DatePicker dp = OperateControlHelper.GetVisualChild<DatePicker>(cell); TextBox txb = OperateControlHelper.GetVisualChild<TextBox>(cell); FrameworkElement node; DependencyProperty depenPro; if (dp != null ) { node = dp; depenPro = DatePicker.TextProperty; } else if (txb != null ) { node = txb; depenPro = TextBox.TextProperty; } else { throw new Exception( "..." ); } InitValidateExistCurMonthOrLogName(node, new ValidateExistCurMonthOrLogName() { Entity = originalModel }); } 2、添加或修改页面直接调用 InitValidateExistCurMonthOrLogName(txbLogName, new ValidateExistCurMonthOrLogName() { Entity = source }); InitValidateExistCurMonthOrLogName(dpCurMonth, new ValidateExistCurMonthOrLogName() { Entity = source }); //调用 void InitValidateExistCurMonthOrLogName(FrameworkElement node, ValidateExistCurMonthOrLogName modelArgs) { //获取类型 DependencyProperty depenPro; if (node is DatePicker) { depenPro = DatePicker.TextProperty; } else { depenPro = TextBox.TextProperty; } //获取自定义验证 ValidateExistCurMonthOrLogName validateLogNameOrCurMonth = node.GetBindingExpression(depenPro).ParentBinding.ValidationRules.Select(v => { if (v is ValidateExistCurMonthOrLogName) return v; return null ; }).FirstOrDefault() as ValidateExistCurMonthOrLogName; if (validateLogNameOrCurMonth != null ) { validateLogNameOrCurMonth.Entity = modelArgs.Entity; } } |
二、styel
1、列表的样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
<Style TargetType= "DataGrid" > <Setter Property= "AutoGenerateColumns" Value= "False" /> <!--<Setter Property= "IsReadOnly" Value= "True" />--> <Setter Property= "VerticalAlignment" Value= "Top" /> <Setter Property= "CanUserSortColumns" Value= "False" /> <Setter Property= "CanUserResizeColumns" Value= "False" /> <Setter Property= "CanUserResizeRows" Value= "False" /> <Setter Property= "SelectionMode" Value= "Extended" /> <Setter Property= "SelectionUnit" Value= "FullRow" /> <Setter Property= "CanUserReorderColumns" Value= "False" /> <Setter Property= "AlternationCount" Value= "2" /> <Setter Property= "RowHeaderWidth" Value= "0" /> <Setter Property= "CanUserAddRows" Value= "False" /> <Setter Property= "CanUserResizeColumns" Value= "false" /> <Setter Property= "Background" Value= "#b7e9fe" /> <Setter Property= "BorderBrush" Value= "gray" /> <Setter Property= "HorizontalGridLinesBrush" > <Setter.Value> <SolidColorBrush Color= "#85cfee" /> </Setter.Value> </Setter> <Setter Property= "VerticalGridLinesBrush" > <Setter.Value> <SolidColorBrush Color= "#85cfee" /> </Setter.Value> </Setter> </Style> <Style TargetType= "DataGridColumnHeader" > <Setter Property= "SnapsToDevicePixels" Value= "True" /> <Setter Property= "MinWidth" Value= "0" /> <Setter Property= "MinHeight" Value= "28" /> <Setter Property= "Foreground" Value= "#07638a" /> <Setter Property= "FontWeight" Value= "Bold" /> <Setter Property= "FontSize" Value= "12" /> <Setter Property= "Cursor" Value= "Hand" /> <Setter Property= "Template" > <Setter.Value> <ControlTemplate TargetType= "DataGridColumnHeader" > <Border x:Name= "BackgroundBorder" BorderThickness= "0,1,0,1" BorderBrush= "#85cfee" Width= "Auto" > <Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width= "*" /> </Grid.ColumnDefinitions> <ContentPresenter Margin= "0,0,0,0" VerticalAlignment= "Center" HorizontalAlignment= "Center" /> <Path x:Name= "SortArrow" Visibility= "Collapsed" Data= "M0,0 L1,0 0.5,1 z" Stretch= "Fill" Grid.Column= "2" Width= "8" Height= "6" Fill= "White" Margin= "0,0,50,0" VerticalAlignment= "Center" RenderTransformOrigin= "1,1" /> <Rectangle Width= "1" Fill= "#85cfee" HorizontalAlignment= "Right" Grid.ColumnSpan= "1" /> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> <Setter Property= "Height" Value= "25" /> </Style> <Style TargetType= "DataGridRow" > <Setter Property= "Background" Value= "#FFFFFF" /> <Setter Property= "Height" Value= "25" /> <Setter Property= "Foreground" Value= "#07638a" /> <Style.Triggers> <Trigger Property= "AlternationIndex" Value= "0" > <Setter Property= "Background" Value= "#FFFFFF" /> </Trigger> <Trigger Property= "AlternationIndex" Value= "1" > <Setter Property= "Background" Value= "#e1f5fd" /> </Trigger> <Trigger Property= "IsMouseOver" Value= "True" > <Setter Property= "Background" Value= "LightGray" /> </Trigger> <Trigger Property= "IsSelected" Value= "True" > <Setter Property= "Foreground" Value= "Black" /> </Trigger> </Style.Triggers> </Style> |
2、DataGrid的ErrorTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
<Style x:Key= "textBoxErrorTemplateInDataGrid" TargetType= "{x:Type TextBox}" > <Setter Property= "VerticalAlignment" Value= "Center" /> <Setter Property= "HorizontalAlignment" Value= "Left" /> <Setter Property= "BorderBrush" Value= "#6bc4e9" /> <Setter Property= "BorderThickness" Value= "1" /> <Setter Property= "MinWidth" Value= "80" /> <Style.Triggers> <Trigger Property= "Validation.HasError" Value= "true" > <Setter Property= "Validation.ErrorTemplate" > <Setter.Value> <ControlTemplate> <DockPanel LastChildFill= "True" > <Ellipse DockPanel.Dock= "Right" Width= "15" Height= "15" Margin= "-25,0,0,0" StrokeThickness= "1" Fill= "Red" > <Ellipse.Stroke> <LinearGradientBrush EndPoint= "1,0.5" StartPoint= "0,0.5" > <GradientStop Color= "#FFFA0404" Offset= "0" /> <GradientStop Color= "#FFC9C7C7" Offset= "1" /> </LinearGradientBrush> </Ellipse.Stroke> </Ellipse> <TextBlock DockPanel.Dock= "Right" ToolTip= "{Binding ElementName=errorHint,Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" Foreground= "White" FontSize= "11pt" Margin= "-15,5,0,0" FontWeight= "Bold" >! <TextBlock.Triggers> </TextBlock.Triggers> </TextBlock> <Border BorderBrush= "Red" BorderThickness= "1" > <AdornedElementPlaceholder Name= "errorHint" /> </Border> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> |
3、添加修改的ErrorTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<Style x:Key= "datePickerErrorTemplate" TargetType= "{x:Type DatePicker}" > <Setter Property= "VerticalAlignment" Value= "Center" /> <Setter Property= "HorizontalAlignment" Value= "Left" /> <Setter Property= "BorderBrush" Value= "#6bc4e9" /> <Setter Property= "Foreground" Value= "#07638a" /> <Setter Property= "Margin" Value= "5, 10, 0, 0" /> <Setter Property= "Width" Value= "120" /> <Setter Property= "Height" Value= "23" /> <Setter Property= "BorderThickness" Value= "1" /> <Style.Triggers> <Trigger Property= "Validation.HasError" Value= "true" > <Setter Property= "Validation.ErrorTemplate" > <Setter.Value> <ControlTemplate> <DockPanel LastChildFill= "True" > <TextBlock DockPanel.Dock= "Right" Margin= "5,0,0,0" VerticalAlignment= "Center" Foreground= "Red" FontSize= "12" Text= "{Binding ElementName=errorHint, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" > </TextBlock> <Border BorderBrush= "Red" BorderThickness= "1" > <AdornedElementPlaceholder Name= "errorHint" /> </Border> </DockPanel> </ControlTemplate> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> |
三、wpf验证中常用的方法
1、获取自定义验证类
1
|
public YourValidationRule GetValidationRule(FrameworkElement node,DependencyProperty depenPro)<br>{ |
1
2
3
4
5
|
<em id= "__mceDel" >YourValidationRule vr = node.GetBindingExpression(depenPro).ParentBinding.ValidationRules.Select(v => { if (v is YourValidationRule ) return v; return null ; }).FirstOrDefault() as YourValidationRule ;<br> return vr;<br>}</em> |
2、递归判断是否有未通过验证的控件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
public static bool IsHasError(DependencyObject node, out string errorMsg) { errorMsg = string .Empty; if (node != null ) { bool isValid = !Validation.GetHasError(node); if (!isValid) { if (node is IInputElement) if (((IInputElement)node).IsEnabled == true ) { ValidationError ve = Validation.GetErrors(node).FirstOrDefault(); if (ve != null ) { errorMsg = ve.ErrorContent.ToString(); } Keyboard.Focus((IInputElement)node); return false ; } } } foreach ( object subnode in LogicalTreeHelper.GetChildren(node)) { if (subnode is DependencyObject) { if (IsHasError((DependencyObject)subnode, out errorMsg) == false ) return false ; } } return true ; } |
3、向控件中添加错误验证
1
2
3
4
5
6
7
8
9
10
11
12
|
public static void AddValidationError<T>(FrameworkElement fe, DependencyProperty dp, string errorMsg) where T : ValidationRule, new () { ValidationError validationError = new ValidationError( new NotConvertInt(), fe.GetBindingExpression(dp)); validationError.ErrorContent = "该用户在本月已存在数据!" ; Validation.MarkInvalid( fe.GetBindingExpression(dp), validationError); } |
4、清空控件中的错误验证
1
2
3
4
|
public static void ClearValidationError(FrameworkElement fe, DependencyProperty dp) { Validation.ClearInvalid(fe.GetBindingExpression(dp)); } |
5、从DataGrid获得Cell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public static DataGridCell GetCell(DataGrid dataGrid, int row, int column) { DataGridRow rowContainer = GetRow(dataGrid, row); if (rowContainer != null ) { DataGridCellsPresenter presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer); if (presenter == null ) { dataGrid.ScrollIntoView(rowContainer, dataGrid.Columns[column]); presenter = GetVisualChild<DataGridCellsPresenter>(rowContainer); } DataGridCell cell = (DataGridCell)presenter.ItemContainerGenerator.ContainerFromIndex(column); return cell; } return null ; } |
6、从DataGrid获得Row
1
2
3
4
5
6
7
8
9
10
11
|
public static DataGridRow GetRow(DataGrid dataGrid, int index) { DataGridRow row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index); if (row == null ) { dataGrid.UpdateLayout(); dataGrid.ScrollIntoView(dataGrid.Items[index]); row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index); } return row; } |
7、获取指定索引项的元素
1
2
3
4
5
6
7
|
public static TContainer GetContainerFromIndex<TContainer> (ItemsControl itemsControl, int index) where TContainer : DependencyObject { return (TContainer) itemsControl.ItemContainerGenerator.ContainerFromIndex(index); } |
8、从DataGrid中获取正在编辑的Row
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public static DataGridRow GetEditingRow(DataGrid dataGrid) { var sIndex = dataGrid.SelectedIndex; if (sIndex >= 0) { var selected = GetContainerFromIndex<DataGridRow>(dataGrid, sIndex); if (selected.IsEditing) return selected; } for ( int i = 0; i < dataGrid.Items.Count; i++) { if (i == sIndex) continue ; var item = GetContainerFromIndex<DataGridRow>(dataGrid, i); if (item.IsEditing) return item; } return null ; } |