Show WPF validation error message in a fixed place - c#

I got WPF validation running (added ValidationRules to the binding) and with the template I can create nice adorners. There are many posting out there.
But I cannot find a way to display the error message outside of the adorned control in a fixed place like a TextBlock in a corner of the window e.g.
How could I achieve this? Can I bind all of my validation error messages to my DataContext (ViewModel here)?
Update: Thanks to an answer I got it partly working. The validation messages are now displayed in another label. As all the textboxes with their validation rules are created on the fly by code, the binding to do so is done this way:
Binding bindSite = new Binding();
bindSite.Source = this.validationErrorDisplayLabel;
BindingOperations.SetBinding(textBox, Validation.ValidationAdornerSiteProperty, bindSite);
But the validation messages are only forwarded to the adornersite for the last textbox for which this code was executed.
I reproduced the problem in this small example.
XAML:
<Grid>
<TextBox
Validation.ValidationAdornerSite="{Binding ElementName=ErrorDisplay}"
HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<TextBox.Text>
<Binding>
<Binding.Path>Box1</Binding.Path>
<Binding.ValidationRules>
<local:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox
Validation.ValidationAdornerSite="{Binding ElementName=ErrorDisplay}"
HorizontalAlignment="Left" Height="23" Margin="10,38,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<TextBox.Text>
<Binding>
<Binding.Path>Box2</Binding.Path>
<Binding.ValidationRules>
<local:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock
x:Name="ErrorDisplay"
Background="AntiqueWhite"
Foreground="Red"
Text="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)[0].ErrorContent}"
HorizontalAlignment="Left" Margin="230,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" RenderTransformOrigin="2.218,-4.577" Width="177" Height="51"/>
</Grid>
The class RuleA produces a validation error when the value equals the string "A". The errors in the 2nd textbox are displayed in the TextBlock, the errors of the first one not (instead it uses the default template and gets a red border).
How can it work for both? The textblock does not need to sum up all errors but display the very first error.

You can use a BindingGroup in combination with the Validation.ValidationAdornerSite and Validation.ValidationAdornerSiteFor properties.
This blog post shows you an example of how to do this.
<StackPanel x:Name="FormRoot"
Validation.ValidationAdornerSite="{Binding ElementName=ErrorDisplay}">
<FrameworkElement.BindingGroup>
<BindingGroup Name="FormBindingGroup" />
</FrameworkElement.BindingGroup>
<TextBox>
<TextBox.Text>
<Binding BindingGroupName="FormBindingGroup"
UpdateSourceTrigger="LostFocus"
Path="Box1">
<Binding.ValidationRules>
<l:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding BindingGroupName="FormBindingGroup"
UpdateSourceTrigger="LostFocus"
Path="Box2">
<Binding.ValidationRules>
<l:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<ItemsControl x:Name="ErrorDisplay"
Background="AntiqueWhite"
Foreground="Red"
ItemsSource="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}"
DisplayMemberPath="ErrorContent" />
</StackPanel>
To commit the values as the user types, change the UpdateSourceTrigger values to PropertyChanged. Note that it isn't strictly necessary to use ValidationAdornerSite here; you could simply point the ErrorDisplay binding directly to the owner of the BindingGroup:
ItemsSource="{Binding ElementName=FormRoot, Path=(Validation.Errors)}"

Thanks to http://www.scottlogic.com/blog/2008/11/28/using-bindinggroups-for-greater-control-over-input-validation.html I was able to solve this with a BindingGroup and without ValidationAdornerSite.
<Window x:Class="BindingAndValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingAndValidation"
Title="Binding and Validation" Height="110" Width="425"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Grid x:Name="RootElement">
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<Grid.BindingGroup>
<BindingGroup Name="LocalBindingGroup">
<BindingGroup.ValidationRules>
<local:RuleGroup />
</BindingGroup.ValidationRules>
</BindingGroup>
</Grid.BindingGroup>
<TextBox
HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120" LostFocus="TextBox_LostFocus">
<TextBox.Text>
<Binding>
<Binding.Path>Box1</Binding.Path>
<Binding.BindingGroupName>LocalBindingGroup</Binding.BindingGroupName>
<Binding.ValidationRules>
<local:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox
HorizontalAlignment="Left" Height="23" Margin="10,38,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<TextBox.Text>
<Binding>
<Binding.Path>Box2</Binding.Path>
<Binding.BindingGroupName>LocalBindingGroup</Binding.BindingGroupName>
<Binding.ValidationRules>
<local:RuleA />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors), ElementName=RootElement}" MinWidth="100" MinHeight="16" Margin="230,10,0,0" Background="AntiqueWhite" Foreground="Red">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Foreground="Red" Content="{Binding Path=ErrorContent}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Button" HorizontalAlignment="Left" Margin="150,0,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click" />
</Grid>
</Window>
The validation only occurs when you call CommitEdit. If you want to have it immediatly like I wished here you can use LostFocus
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
this.RootElement.BindingGroup.CommitEdit();
}
Of course for a greater project an attached property might help.

Related

XAML binding multiple properties in label content

I have two properties, Related_Id and PageNumber. I want to bind these two values to a single label.
XAML code
<StackPanel>
<sdk:Label x:Name="RelatedItemIdLabel"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="{Binding CreateMessage.RelatedId}" />
</StackPanel>
current output: Related_Id
Desired output: Related_Id/ PageNumber
Could anyone help me to find a solution..
Thanks..
Try this:
<Label x:Name="RelatedItemIdLabel"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<Label.Content>
<MultiBinding StringFormat=" {0}/{1}">
<Binding Path="" /> //insert field 1
<Binding Path="" /> //insert field 2
</MultiBinding>
</Label.Content>
</Label>
This is the code you're looking for :
<StackPanel>
<sdk:Label x:Name="RelatedItemIdLabel"
HorizontalAlignment="Left"
VerticalAlignment="Top">
<sdk:Label.Content>
<MultiBinding StringFormat=" {0}, {1}">
<Binding Path="{Binding CreateMessage.RelatedId}"/>
<Binding Path="{Binding CreateMessage.PageNumber}"/>
</MultiBinding>
</sdk:Label.Content>
</sdk:Label>
</StackPanel>

System.Windows.Controls.TextBox.Text property has already been set and can only be set once

I'm trying to create a simple validation for a textBoxhowever I'm geting the fallowing error in my XAML.
System.Windows.Controls.TextBox.Text property has already been set and
can only be set once.
As you can see from my code I don't havn't set the property twice. Not sure why this is happening.
My code is as fallows
<TextBox x:Name="tbxName" HorizontalAlignment="Center" Grid.Row="4" Grid.Column="1" VerticalAlignment="Top" Height="23" TextWrapping="Wrap" Width="120" Validation.ErrorTemplate="{StaticResource validationErrorTemplate}">
<TextBox.Text>
<Binding Path="Name" Mode="TwoWay" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<local:NameValidator></local:NameValidator>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

Click event on custom control in list view not always setting SelectedItem

I have a situation where I have a listview :
<ListView ItemsSource="{Binding Environments}" SelectedItem="{Binding SelectedEnvironment}">
<ListView.ItemTemplate>
<DataTemplate>
<controls:RadioButtonTextBox DataContext="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
which uses a custom control as its item template:
<StackPanel Orientation="Horizontal">
<RadioButton VerticalAlignment="Center">
<RadioButton.IsChecked>
<MultiBinding Converter="{converters:StringCompareToBooleanConverter}">
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path="SelectedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
<TextBlock Text="{Binding}" VerticalAlignment="Center" Margin="5,0,0,0" Style="{DynamicResource RedTextBlock}"/>
</StackPanel>
And the problem I have is that if the user clicks on the textblock of the custom control then the correct thing happens, i.e. the item is selected (and the view model updates accordingly), yet if the user click on the radio button of the custom control the radio button becomes checked but the selected item is not updated and the previously selected item does not get deselected.
Can anyone help with this issue?
The click is handled and not propagated by your radio button.
In cases where the item contains input elements i tend to bind IsSelected in the ItemContainerStyle to IsKeyboardFocusWithin. Not sure if this deselects the old item, maybe only if selection mode is Single.
The simplest solution I found was to simply set the enabled flag on the radio button to false:
<StackPanel Orientation="Horizontal">
<RadioButton VerticalAlignment="Center" IsEnabled="False">
<RadioButton.IsChecked>
<MultiBinding Converter="{converters:StringCompareToBooleanConverter}">
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=UserControl}"/>
<Binding Path="SelectedItem" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
<TextBlock Text="{Binding}" VerticalAlignment="Center" Margin="5,0,0,0" Style="{DynamicResource RedTextBlock}"/>
</StackPanel>

How do I make error the same size as textbox and label?

So I setup a textbox and label:
<TextBox Height="23" HorizontalAlignment="Right" Margin="0,74,119,0" Name="txb_idleTime" VerticalAlignment="Top" Width="162">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<TextBox.Text>
<Binding Path="MinIdleTime" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<Local:NumberValidation />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Label Content="Minimum Idle Time (min):" Height="28" HorizontalAlignment="Left" Margin="65,72,0,0" Name="label1" VerticalAlignment="Top" Width="160" HorizontalContentAlignment="Right" Target="{Binding}" />
Currently the error message display directly under the textbox like this:
What I would like it to look like is this:
How do I do this?
Specify negative left margin to error textBlock equal to width of label. This should work:
<TextBlock Text="{Binding [0].ErrorContent}" Margin="-160,0,0,0"
Foreground="Red"/>
I'd probably go with this Approach: MSDN Validation. I haven't used it yet but sounds as if it fits your needs.

wpf validation - how can I get this code to trigger a validation as typing occurs (cf when leaving the field)

how can I get this code to trigger a validation as typing occurs (cf when leaving the field). The code below works OK in terms of validation, however it does not work until leaving the field (not as you type).
XAML
<Grid.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
.
.
.
<TextBox IsEnabled="{Binding ElementName=ProxyModeRadioButton, Path=IsChecked}"
Width="Auto" Name="ProxyHostTextBox" VerticalAlignment="Center" MinWidth="150" >
<TextBox.Text>
<Binding Path="Proxy" >
<Binding.ValidationRules>
<local:SpecialCharactersRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
thanks
try
<TextBox IsEnabled="{Binding ElementName=ProxyModeRadioButton, UpdateSourceTrigger=PropertyChanged, Path=IsChecked}" Width="Auto" Name="ProxyHostTextBox" VerticalAlignment="Center" MinWidth="150" >
<TextBox.Text>
<Binding Path="Proxy" >
<Binding.ValidationRules>
<local:SpecialCharactersRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Note the UpdateSourceTrigger=PropertyChanged in the binding.
UPDATE
As blindmeis stated below, I put the UpdateSourceTrigger in the wrong Binding box.. my mistake. It should go with the TextBox.Text. Sorry about that...
<TextBox IsEnabled="{Binding ElementName=ProxyModeRadioButton, Path=IsChecked}" Width="Auto" Name="ProxyHostTextBox" VerticalAlignment="Center" MinWidth="150" >
<TextBox.Text>
<Binding Path="Proxy" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:SpecialCharactersRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Dave is nearly right but i think you want your validation occur when your TEXT property change, so you have to add the UpdateSourceTrigger=PropertyChanged to the TEXT binding
<TextBox IsEnabled="{Binding ElementName=ProxyModeRadioButton, Path=IsChecked}"
Width="Auto" Name="ProxyHostTextBox" VerticalAlignment="Center" MinWidth="150">
<TextBox.Text>
<Binding Path="Proxy" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:SpecialCharactersRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>

Categories

Resources