雖然標題是wpf數據驗證,但並不是對IDataErrorInfo、ValidationRule、屬性中throw Exception這幾種驗證方式的介紹;
之前做項目時(例如員工工資管理),根據員工編號和年度月份驗證 當月數據的唯一性,因為第一次開發wpf經驗不足,所以用過幾種不同的方式,並且現在用的這種方式也不是很滿意,如果哪位大神有更好的辦法 麻煩發個鏈接。
文章最后會把驗證、列表的樣式和驗證中常使用的一些方法貼出來,方便大家使用;
列表頁面 員工編號和年度月份驗證
添加修改頁面 填寫編號選擇月份后,驗證不通過都是在編號處提示
一、言歸正傳,逐步寫一下我當時的思路
1、為了實現這種需求的驗證,最先想到的就是實現了ValidationRule的自定義驗證類(ValidateExistLogName)能有一個屬性(ValiByProperty) binding上月份中選擇的值,關聯月份和當前輸入的員工編號來驗證當月是否存在;
<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才能顯示出來錯誤
列表中 初始化列表時遍歷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的類中添加需要驗證的實體屬性
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);
//判斷當前編輯的是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、列表的樣式
<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
<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
<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、獲取自定義驗證類
public YourValidationRule GetValidationRule(FrameworkElement node,DependencyProperty depenPro)
{
YourValidationRule vr = node.GetBindingExpression(depenPro).ParentBinding.ValidationRules.Select(v => { if (v is YourValidationRule ) return v; return null; }).FirstOrDefault() as YourValidationRule ;
return vr;
}
2、遞歸判斷是否有未通過驗證的控件
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、向控件中添加錯誤驗證
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、清空控件中的錯誤驗證
public static void ClearValidationError(FrameworkElement fe, DependencyProperty dp) { Validation.ClearInvalid(fe.GetBindingExpression(dp)); }
5、從DataGrid獲得Cell
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
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、獲取指定索引項的元素
public static TContainer GetContainerFromIndex<TContainer> (ItemsControl itemsControl, int index) where TContainer : DependencyObject { return (TContainer) itemsControl.ItemContainerGenerator.ContainerFromIndex(index); }
8、從DataGrid中獲取正在編輯的Row
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; }