Skip to content

GuOrg/Gu.Wpf.ValidationScope

Repository files navigation

Gu.Wpf.ValidationScope

License Gitter NuGet Build status Build Status

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:

*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:

ItemsSource2D render

More samples in the demo project

2. Scope

2.1. ForInputTypes

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.

2.1.1. Sample: Defining a scope for textboxes.

<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>

2.1.2. Sample: Defining a partial scope for textboxes.

<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>

2.1.3 Valid values

Types must be elements deriving from UIElement

2.1.3.1 Typenames

<Border validation:Scope.ForInputTypes="TextBox, ComboBox" ...>

2.1.3.2 Fully qualified Typenames

<Border validation:Scope.ForInputTypes="System.Windows.Controls.TextBox, ComboBox" ...>

2.1.3.3 IEnumebarble

<Border validation:Scope.ForInputTypes="{Binding ScopeTypes}" ...>

2.1.3.4 InputTypeCollection.Default

<Border validation:Scope.ForInputTypes="{x:Static validation:InputTypeCollection.Default}" ...>

2.1.3.5 InputTypes markupextension

<Border validation:Scope.ForInputTypes="{validation:InputTypes {x:Type TextBox}, {x:Type ComboBox}}" ...>

2.2. HasError

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>

2.3. Errors

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>

2.4. Node

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>

2.5. ErrorEvent

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>

2.6. ErrorsChangedEvent

An event that notifies when errors are added and removed. The ErrorEvent notifies about each add and remove while ErrorsChangedEventnotifies 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>

2.7. OneWayToSourceBindings

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>

3. InputTypeCollection

3.1. Default

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

4. InputTypes markupextension

Exposed for convenience to create list of types in xaml.

<Border validation:Scope.ForInputTypes="{validation:InputTypes {x:Type TextBox}, {x:Type ComboBox}}">

5. Node

The validation scope is a tree of nodes.

5.1. HasError

A bool indicating if there is a validation error in the scope. Similar to System.Controls.Validation.HasError

5.2. Errors

A ReadOnlyObservableCollection<ValidationError> with the errors in the scope. Similar to System.Controls.Validation.Errors

5.3. Children

A ReadOnlyObservableCollection<ErrorNode> with the child nodes which have errors in the scope.

5.4 Node types

5.4.1 InputNode

This node type is used for elements for which we track errors.

5.4.2 ScopeNode

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.

5.4.3 ValidNode

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.