WPF Validation Result Popup - wpf

I'm trying to implement WPF validation on a few text boxes. I want to be able to display the validation errors to the user with something more obvious than a tool-tip, but more subtle than a dialog or messagebox. I settled on using the Popup class, rather than some built in textbox for displaying errors, since there are many different fields that need to be validated this way, and I want the feedback to be "attached" to the field in question.
The problem I'm experiencing is that the binding of the Popup's child TextBox to the attached TextBox's (Validation.Errors) property is not updated aggressively enough. As soon as there is an Error object, the text is updated and displayed (i.e. "Please enter a name." for an empty field) but if the error changes (i.e. the user enters invalid text) the message in the popup stays the same... until/unless they enter valid input, at which point the popup disappears as desired.
I've done some debugging, and I've found that while the validation rule is being called correctly and returning the proper results, the converter for the Popup is only called at the creation of the initial error. I guess what I am confused about is why the Popup gets updated only when the validation state goes from "no errors" to "some error(s)", so to speak. Does anyone know a way to force the changes to Validation.Errors to be reflected in my Popup's TextBox.Text?
Here's a xaml example of what I've written.
<TextBox Name="MyTextBox">
<TextBox.Text>
<Binding Path="MyText" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:MyTextBoxValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Popup Name="MyPopup" IsOpen="{Binding ElementName=MyTextBox,
Path=(Validation.HasError), Mode=OneWay}">
<Border BorderThickness="1" BorderBrush="Red" Background="White">
<TextBlock Foreground="Red" Margin="5 5 5 5"
Text="{Binding ElementName=MyTextBox, Path=(Validation.Errors),
Converter={StaticResource errorsToMessageConverter}}"/>
</Border>
</Popup>

I was able to find a compromise. I changed the binding on the Popup's TextBlock like so:
<TextBlock Name="MyPopupTextBox" Foreground="Red" Margin="5 5 5 5"
Text="{Binding ElementName=MyTextBox, Path=(Validation.Errors)[0].ErrorContent,
UpdateSourceTrigger=PropertyChanged, Mode=OneWay, NotifyOnValidationError=True,
NotifyOnSourceUpdated=True, ValidatesOnExceptions=True}"/>

Your original problem is a typical problem when binding to a collection: As long as your collection does not change structurally (insert/remove/clear), the binding will not see any reason to update. So if only an item in the collection changes some property, this does not cause your binding to update.
You have already solved this by binding to the element in this collection, this way the full path to the changed property (the ErrorContent) will be observed in the binding.
The problem with binding (Validation.Errors)[0] is that it raises IndexOutOfRange-Exceptions (not thrown, but they are there) if the Error-Collection is empty.
This thread discusses that exception and also contains a workaround that works fine for me:
http://social.msdn.microsoft.com/forums/en/wpf/thread/1c6cd214-abce-4a8b-a919-0726dd81461a/
Just replace (Validation.Errors)[0].ErrorContent by (Validation.Errors).CurrentItem.ErrorContent and you are done.

Related

Textbox does not have KeyboardFocus when typing, KeyBindings not working as a result

I'm using the material design in XAML library and my TextBoxes are acting strange. When you type something into them, the KeyboardFocus is not set on the TextBox, so the keybindings don't work. When you click the TextBox it has focus and the keybindings work, but as soon you start typing something, you lose focus and and must re-click the TextBox to get focus back.
<TextBox
materialDesign:HintAssist.Hint="Type something..."
Style="{DynamicResource MaterialDesignFloatingHintTextBox}"
Text="{Binding Name}"> <!--this binding works-->
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SearchCommand}"/>
</TextBox.InputBindings>
</TextBox>
I found the solution to my Problem. It was not a focus problem, instead nothing happened, because the updatesourcetrigger was set to lostfocus by default, so when you press enter in my Application nothing would happen, because you still had the same Entry(Binding Name) as before and it would be ignored. So the solution was setting it like this.
Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"

WPF ElementName two-way binding: Why does instant update work one way but not the other?

I have two text boxes on a WPF form. The second TextBox gets its text from the first TextBox using ElementName binding, and it also updates the first text box via TwoWay binding mode:
<StackPanel Orientation="Vertical">
<TextBox Name="TextBox1"/>
<TextBox Text="{Binding ElementName=TextBox1, Path=Text, Mode=TwoWay}"/>
</StackPanel>
When I edit the second text box, the first text box updates only when the second text box loses focus, not instantly as I type. (This is as expected, UpdateSourceTrigger=PropertyChanged can be added to the binding to get instant update.)
However when I edit the first text box, the second text box updates instantly as I type. This was not what I expected. Why is this?
DependencyProperty Text of any TextBox updates when a new symbol is typed (can be tested by attaching a handler to TextChanged event)
But there is a connector between two DPs: a Binding (image from MSDN article)
Binding intercept changes of Target DP and update Source Property according to UpdateSourceTrigger parameter. Changes from Source are visible immediately.
Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source. This is known as updating the source. Usually, these updates happen whenever the target property changes. This is fine for check boxes and other simple controls, but it is usually not appropriate for text fields. Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. Therefore, the default UpdateSourceTrigger value of the Text property is LostFocus and not PropertyChanged.
to have a simmetric behavior, use binding for both TextBoxes
<StackPanel Orientation="Vertical">
<TextBox Name="TextBox1" Text="{Binding Path=Text, ElementName=Txt, Mode=TwoWay}"/>
<TextBlock Name="Txt" Visibility="Collapsed"/>
<TextBox Name="TextBox2" Text="{Binding Path=Text, ElementName=Txt, Mode=TwoWay}"/>
</StackPanel>
Well, this is expected behavior.
Imagine, you bind a Label or (TextBlock) to a property of data class, e.g. Person.Name:
<Label Content="{Binding Person.Name}" />
You expect the label to be updated, whenever Person.Name changes.
In this case, the Person is your binding source and Label binding target.
Now back to your case. You have two textboxes, where second is databound to first. The first is binding source and the second is binding target. Whenever source changes, target is updated, just like if it was databound to Person.Name. UpdateSourceTrigger argument affects only when the source should be updated, as the argument name suggests.
WORKAROUND:
Databind both TextBoxes to 3rd object (usually it is ViewModel).
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding SomeProperty}"/>
<TextBox Text="{Binding SomeProperty}"/>
</StackPanel>

How to handle Validations on Custom Control

I recently wrote my first custom control, an autocomplete textbox. Its Controltemplate consists of something like this
<Grid >
<Border/>
<TextBlock x:Name="Label"/>
<TextBox x:Name="TextLabel"/>
</Grid>
The first Textblock is used as a Label, the second one shows the content. The TextLabel binds on an Object, let's call it Customer If the underlying search doesn't find a Customer object, I want to show the errortemplate. When defining the TextLabel like this
<TextBox x:Name="PART_Editor"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}"
Style="{StaticResource TransparentTextBoxStyle}"
Text="{Binding Path=Text, RelativeSource={RelativeSource Mode=TemplatedParent},
Mode=TwoWay, ValidatesOnNotifyDataErrors=True,
NotifyOnValidationError=True,
ValidatesOnDataErrors=True,
UpdateSourceTrigger=PropertyChanged}" >
</TextBox>
the Validation is made and the error template of the textbox is shown. Unfortunately the Red Border is only around the inner TextBox and not around the whole custom control which looks just not good.
I was wondering how to achieve two things:
How can the ErrorTemplate of the CustomControl be triggered when one of the child-validations fail?
Do I have to specify all these NotifyOnValidationerror properties or is it possible to catch all errors on entity level and show the same ErrorTemplate
If you need additional Information, please just ask

Using converter in Validation.Errors binding

In my WPF application (.Net 4.5) I want to provide extended visual feedback about validation results to the UI. The data layer's validation engine returns warnings and errors via INotifyDataErrorInfo interface.
I have the following XAML to display red or orange border depending on the error type and the list of error messages. Here errorToColor is a resource key of the value converter which returns red brush if there is at least one error in the Validation.Errors collection and orange brush if there are only warnings.
<TextBox Name="MappingName" Text="{Binding Path=Mapping.Name, NotifyOnValidationError=True}" >
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="{Binding Converter={StaticResource errorsToColor}}" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
<ListView DisplayMemberPath="ErrorContent" ItemsSource="{Binding}" />
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
Now let's see what happens when I type some 'not valid' text in the TextBox.
Typed 'Text1' and changed focus.Debugger stepped into the converter and both validators resulting in two items in the ListView (1 error and 1 warning) and a red border. [OK]
Typed 'Text' to correct the error, changed focus.
The value converter wasn't even hit! Of course, the same red border. But the ListView has changed and shows only one warning.
Can somebody explain what's going on? Why the ListView receives collection change notification and the Border not? Is it because ListView is an ItemsControl and the Validation.Errors is wrapped into the CollectionView?
For those who are interested. The errorsToColor converter wasn't fired because the Validation.Errors collection didn't raise PropertyChanged event (needed trigger binding coverter) when errors were added or removed.
In order to raise PropertyChanged event we need to bind to a property which is changed on each error is added, for example Count. I still need the Errors collection itself in the converter, so I used a multi-binding here.
<Border BorderThickness="1">
<Border.BorderBrush>
<MultiBinding Converter="{StaticResource errorsToColor}">
<Binding Path="." />
<Binding Path=".Count" />
</MultiBinding>
</Border.BorderBrush>
<AdornedElementPlaceholder Name="adornedElement" />
</Border>
Now the errorsToColor converter (which is now implements IMultiValueConverter) is executed every time new error is added / removed.

WPF: Validation.ErrorTemplate not displaying

I have a template for displaying a red border and an error message around controls. It works (tested on TextBoxes and ComboBoxes). But on two particular comboboxes they don't.
Well let's see what's different in the VM:
since I have common validation implementation in my base class, no change there
the same kind of asynchronously loaded data is displayed that works well with validation with just one panel over
so in short, no difference in VM.
The view is completely the same, the same style is applied, so in short no difference there either.
So I added NotifyOnValidationError=True to the ValidatesOnDataErrors=True already there, and subscribed to Validation.Error... And it fired!!! Yet the template still doesn't show. I'm out of ideas, please suggest me things to check!
EDIT: further research:
I've decompiled DataErrorValidationRule, and recompiled it as MyDataErrorValidationRule to match the original as close as possible. I removed ValidatesOnDataErrors=True, and added my ValidationRule to debug. It returned new ValidationResult(false, (object)str); with str containing the correct error message twice - once for setting the property to null, and once for forcefully validating the entire object. Template still not showing.
I've also checked Validation.GetErrorTemplate on the control (at the time of the first firing of Validation.Error) and it was NOT NULL, so it's not the DynamicResource that failed either.
EDIT: working example:
<ItemsControl ItemsSource="{Binding QuestionAnswers}">
<ItemsControl.Resources>
<!-- ... -->
<DataTemplate DataType="{x:Type Model:QuestionAnswerModel}">
<StackPanel>
<!-- here is the combo box -->
<ComboBox Margin="8,4" Padding="8" MinWidth="120" HorizontalAlignment="Left"
Validation.ErrorTemplate="{DynamicResource DefaultValidationErrorTemplate}"
ItemsSource="{Binding Options.Source}"
DisplayMemberPath="ItemName" SelectedValuePath="ItemID"
SelectedValue="{Binding Options.SelectedID, ValidatesOnDataErrors=true}" />
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
non-working example:
<ComboBox Margin="8,4" Padding="8" MinWidth="120" HorizontalAlignment="Left"
Validation.ErrorTemplate="{DynamicResource DefaultValidationErrorTemplate}"
SelectedItem="{Binding Type.SelectedItem, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" Validation.Error="ComboBox_Error"
ItemsSource="{Binding Type.Source}"
DisplayMemberPath="Localized"
>
They are from the same xaml file, the ItemsControl containing the working ComboBox is in the same Grid as the non-working ComboBox.
The only difference is whether SelectedItem or SelectedValue is bound, but that shouldn't have any bearings on the validation...
I had the exact problem with the error template not displaying even though the event was firiing, and could never figure out why it only happened for some controls and not others.
The workaround I eventually found was to set the ValidationStep to ConvertedProposedValue on the ValidationRule for the binding:
<TextBox>
<TextBox.Text>
<Binding Path="MyField">
<Binding.ValidationRules>
<Validation:MyValidationRule ValidationStep="ConvertedProposedValue" Message="Please enter a value." />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
That seemed to do the trick for me anyway!
EDIT: If you are using IDataErrorInfo, you could try (though I haven't personally tested it):
<Binding Path="MyField" ValidatesOnExceptions="True">
<Binding.ValidationRules>
<DataErrorValidationRule ValidationStep="ConvertedProposedValue" />
</Binding.ValidationRules>
</Binding>
i.e. remove ValidatesOnDataErrors=True, which is just a shortcut for including a single <DataErrorValidationRule />
i would check the following:
check datacontext and binding for your combobox, if it works and IDataErrorInfo is called (i assume you do IDataErrorInfo validation) - next step
copy your validation template as a local resource to your ComboBox.Resources and check if it works
nevertheless it would be good if you post your validation template, your combobox xaml and your datacontext

Resources