虽然标题是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
;
}
|