Library that provides functionality for form validation in WPF.
It works by adding bindings to Validation.Errors for elements in a validation scope.
As bindings are somewhat expensive no bindings are added by default. The types to track errors for are specified using the Scope.ForInputTypes
The samples assumes an xml namespace alias xmlns:validation="https://github.com/JohanLarsson/Gu.Wpf.ValidationScope"
is defined.
- 1. Sample:
- 2. Scope
- 3. InputTypeCollection
- 4. InputTypes markupextension
- 5. Node
*BoolToBrushConverter is not included in the nuget. Check the demo project if interested.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border BorderBrush="{Binding Path=(validation:Scope.HasError),
Converter={local:BoolToBrushConverter WhenTrue=Red, WhenFalse=Black},
ElementName=Form}"
BorderThickness="1">
<Grid x:Name="Form"
validation:Scope.ForInputTypes="TextBox">
<!-- this is where we define our scope, we do so by telling the scope what types of control sto track -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="IntValue1" />
<TextBox Grid.Row="0"
Grid.Column="1"
Text="{Binding IntValue1,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1"
Grid.Column="0"
Text="IntValue2" />
<TextBox Grid.Row="1"
Grid.Column="1"
Text="{Binding IntValue2,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Border>
<ItemsControl Grid.Row="1"
ItemsSource="{Binding Path=(validation:Scope.Errors),
ElementName=Form}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type ValidationError}">
<TextBlock Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Renders:
More samples in the demo project
By setting ForInputTypes
we specify what type of elements to track in the validation scope. Most of the times only TextBoxes will be relevant.
The ForInputTypes
inherits so setting it on one element sets it to all chidlren unless explicitly set to something else for a child.
Setting validation:Scope.ForInputTypes="{x:Null}"
means that errors are not tracked for nodes below this element.
Setting validation:Scope.ForInputTypes="Scope"
means that only errors from subscopes are tracked.
The default value is null meaning no scope is defined.
<Border validation:Scope.ForInputTypes="TextBox">
<StackPanel>
<!--The stackpanel will inherit the scope-->
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
<ComboBox ItemsSource="{Binding Values}" Text="{Binding Value3}" />
<!-- this combobox will not be tracked because the scope is only for textboxes and sliders-->
</StackPanel>
</Border>
<Border validation:Scope.ForInputTypes="Scope">
<StackPanel validation:Scope.ForInputTypes="TextBox">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
<StackPanel validation:Scope.ForInputTypes="{x:null}">
<!-- No tracking of errors for these textboxes, due to ForInputTypes="{x:null}". -->
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
</Border>
Types must be elements deriving from UIElement
<Border validation:Scope.ForInputTypes="TextBox, ComboBox" ...>
<Border validation:Scope.ForInputTypes="System.Windows.Controls.TextBox, ComboBox" ...>
<Border validation:Scope.ForInputTypes="{Binding ScopeTypes}" ...>
<Border validation:Scope.ForInputTypes="{x:Static validation:InputTypeCollection.Default}" ...>
<Border validation:Scope.ForInputTypes="{validation:InputTypes {x:Type TextBox}, {x:Type ComboBox}}" ...>
A bool indicating if there is a validation error in the scope. Similar to System.Controls.Validation.HasError
<Border BorderBrush="{Binding Path=(validation:Scope.HasError),
Converter={local:BoolToBrushConverter WhenTrue=Red, WhenFalse=Transparent},
ElementName=Form}"
BorderThickness="1">
<StackPanel x:Name="Form" validation:Scope.ForInputTypes="TextBox">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
</Border>
A ReadOnlyObservableCollection<ValidationError>
with the errors in the scope. Similar to System.Controls.Validation.Errors
<StackPanel>
<StackPanel x:Name="Form" validation:Scope.ForInputTypes="TextBox">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
<TextBlock Foreground="Red" Text="{Binding (validation:Scope.Errors).Count, ElementName=Form, StringFormat='Errors: {0}'}" />
</StackPanel>
A Node
in the tree that is the validation scope.
<StackPanel>
<StackPanel x:Name="Form" validation:Scope.ForInputTypes="TextBox">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
<TextBlock Foreground="Red" Text="{Binding (validation:Scope.Node).Children.Count, ElementName=Form, StringFormat='Children: {0}'}" />
</StackPanel>
An event that notifies when errors are added and removed. Similar to System.Windows.Controls.Validation.ErrorEvent
Does not require bindings to have NotifyOnValidationError=True
<StackPanel>
<StackPanel validation:Scope.ForInputTypes="TextBox"
validation:Scope.Error="OnValidationError">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
</StackPanel>
An event that notifies when errors are added and removed. The ErrorEvent
notifies about each add and remove while ErrorsChangedEvent
notifies once with a all added and removed events.
Does not require bindings to have NotifyOnValidationError=True
<StackPanel>
<StackPanel validation:Scope.ForInputTypes="TextBox"
validation:Scope.Error="OnValidationError">
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
</StackPanel>
WPF does not allow binding readonly dependency properties even with Mode=OneWayToSource
.
As a workaround for this OneWayToSourceBindings can be used like this:
<StackPanel x:Name="Form" validation:Scope.ForInputTypes="TextBox">
<validation:Scope.OneWayToSourceBindings>
<validation:OneWayToSourceBindings Errors="{Binding Errors}"
HasError="{Binding HasError}"
Node="{Binding Node}" />
</validation:Scope.OneWayToSourceBindings>
<TextBox Text="{Binding Value1}" />
<TextBox Text="{Binding Value2}" />
</StackPanel>
Contains the following types { typeof(Scope), typeof(TextBoxBase), typeof(Selector), typeof(ToggleButton), typeof(Slider) }
And should be enough for most scenarios when you don't have third party controls for example a third party textbox that does not derive from TextBoxBase
Exposed for convenience to create list of types in xaml.
<Border validation:Scope.ForInputTypes="{validation:InputTypes {x:Type TextBox}, {x:Type ComboBox}}">
The validation scope is a tree of nodes.
A bool indicating if there is a validation error in the scope. Similar to System.Controls.Validation.HasError
A ReadOnlyObservableCollection<ValidationError>
with the errors in the scope. Similar to System.Controls.Validation.Errors
A ReadOnlyObservableCollection<ErrorNode>
with the child nodes which have errors in the scope.
This node type is used for elements for which we track errors.
This node type is used for elements which has subnodes with errors. This node type does not listen to validation errors for its source element.
This node type is used for elements which has no errors or are not in a scope. This is immutable and a single instance is used for all.