WPF: Validation.ErrorTemplate not displaying - wpf

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

Related

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

TextBox ConvertBack event doesn't fire for XML element

ValueFormattingConverter.Convert is called with the XmlElement. ConvertBack is never called. Why? Is there some obligation to pass binding directives down the chain? Is the use of the TextBox overriding its own binding settings? What can be done?
My TextBox
<TextBox Width="200"
Text="{Binding Path=., Converter={StaticResource valueFormattingConverter}}",
Mode=TwoWay,
NotifyOnSourceUpdated=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}" />
Usage is rather convoluted. Starting at the top, we provide an XML element to a tab.
<TabItem.DataContext>
<Binding Source="{StaticResource mcf}",
XPath="mdf/press_information"/>
</TabItem.DataContext>
That tab contains a ItemsControl which builds TextBoxes through this ControlChooser which passes the binding along.
<ItemsControl.ItemTemplate>
<DataTemplate>
<W3V:ControlChooser RelativeSource="{RelativeSource AncestorType=W3V:ObjectList}",
Content="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
My converter class header. Convert method is called. ConvertBack never.
[ValueConversion(typeof(XmlElement), typeof(string))]
public class ValueFormattingConverter : IValueConverter
EDIT: The chosen answer basically says Path=. doesn't support 2-way binding. I believe it is the correct answer to the question. Very helpful to know, but "can't do that" doesn't solve the larger problem. So I have laid out the larger question here: Means of generating an editable form from XML.
The binding direction to source won't work with a {Binding Path=.}. This is because there is no bound property, but just the binding source object.
Hence there will never be a source update, and the ConvertBack method is never called, because that would mean to replace the source object.
In order to make your code work, you would have to bind to some property:
<TextBox Text="{Binding Path=SomeElement, ...}"/>

Display selected combobox item according to current item in parent datacontext

I have a wpf app that displays a Label in read-only mode and a ComboBox in edit mode. When the user clicks the 'Edit' button, the label is hidden and the ComboBox becomes visible (using a BooleanToVisibilityConverter).
The ItemsSource of the ComboBox is set to its lookup table (via auditorsViewSource), and the DataContext of the TextBox is set to the parent table (via auditStatementsViewSource).
Here is my xaml:
<!-- Auditor info -->
<StackPanel Orientation="Horizontal" DataContext="{StaticResource auditStatementsViewSource}">
<StackPanel Orientation="Horizontal" >
<Label Content="Auditor:" Name="lblAuditor" />
<!-- readonly data -->
<StackPanel Style="{StaticResource VisibleWhenReadOnly}">
<TextBox Name="txtAuditor" >
<TextBox.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="Auditor.GivenName"/>
<Binding Path="Auditor.Surname"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</StackPanel>
<!-- editable data -->
<StackPanel Style="{StaticResource CollapsedWhenReadOnly}" >
<ComboBox x:Name="cboAuditor" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Surname"
ItemsSource="{Binding Source={StaticResource auditorsViewSource}}"
SelectedItem="{Binding Source=txtAuditor, Path=Auditor.Surname, Mode=TwoWay}" />
</StackPanel>
</StackPanel>
</StackPanel>
The ComboBox displays the items from the base (auditors) table correctly, but displays the first record in the table (according to the ORDER BY clause). How do I get it to initially show the item from the current (auditStatements) record, (ie, the Auditor that was being displayed by the readonly label), preferably in xaml?
Normally, Binding to the ComboBox.SelectedItem property works best if you are Binding an actual element from the collection that is data bound to the ComboBox.ItemsSource property. As you are not using an actual element from the ItemsSource collection, you might have to do things somewhat differently.
On possible solution is for you to use the SelectedValue, SelectedValuePath and DisplayMemberPath properties instead. Please see the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page on MSDN for full details on using this method to access the selected item.

How do I bind combobox text to legacy data not in the drop down list?

The drop-down list (itemssource) of my combobox contains new product request items. I want to bind to legacy data that is not in the drop-down list. To complicate things I'm using multibinding with an IMultiValueConverter to combine fields for display. Also, the names of bound fields do not match the names of the properties I'm bound to.
The combobox itemssource is a list of NewProductRequests. From this NPR object NewProdNumber and NewProdName are combined for display in the drop-down list by my type converter. The ConvertBack method returns the values NewProdNumber and NewProdNumberCombinedWithName. These two values will be saved to database fields with slightly different names. For this example I'll call them DBProdRequestNumber and DBProdRequestTitle.
I've succeeded in displaying and saving new items. The problem is I haven't figured out how to display legacy data that is not in the list. It's not in the list because it no longer qualifies as a new product request.
Here is the problem XAML (the itemssource is set in code-behind):
<ComboBox x:Name="NPRComboBox" IsSynchronizedWithCurrentItem="False" IsEditable="False">
<ComboBox.SelectedItem>
<MultiBinding Converter="{StaticResource combineNPRStuffMultiConverter}">
<Binding Path="DBProdRequestNumber" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="DBProdRequestTitle" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</ComboBox.SelectedItem>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock DataContext="{Binding}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource combineNPRStuffMultiConverter}">
<Binding Path="NewProdNumber" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="NewProdNumberCombinedWithName" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
A similar problem with a datagrid and combobox I solved using a DataGridTemplateColumn.CellEditingTemplate based on this MSDN Magazine example from Julie Lerman. Of course, in this case I'm not using a datagrid.
Thanks in advance for any help.
This answer (to my own question) was pulled from a comment in the NathanAW answer:
Unfortunately I can't include legacy items in the ItemsSource. The list is from a web service that is out of my control. I devised a kludgy solution which I don't really like (but it works)...Since I know the combobox is needed only for new records it is visible only when the user clicks "Add". In the same location I placed a textbox bound to the legacy data that is visible when NOT in add mode. So, I toggle the visiblity of each control as the app switches in and out of add mode. I'm sure there is a better way!
It seems that you might be able to simplify this by not using a Multi-Binding converter. If you have a collection of NPR objects, then you can set that as the ItemsSource for the listbox. Then use the DataTemplate to format how you want that item displayed.
With this setup, you can construct a template that shows multiple fields from the NPR object in a single TextBlock using something like:
<ComboBox
x:Name="NPRComboBox"
IsSynchronizedWithCurrentItem="False"
IsEditable="False"
SelectedItem={Binding SelectedNPR, Mode=TwoWay}"
>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Path=NewProdNumber, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
<Run> - </Run>
<Run Text="{Binding Path=NewProdNumberCombinedWithName, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
If you have additional properties on the NPR object that you'd like to access, you can add an additional section to the template.
Notice that the "selected" item is bound two-way back to a property on your ViewModel (or code-behind, or whatever). This would be something like:
public NPR SelectedNPR
{
get { ... }
set
{
...
// don't forget INotifyPropertyChanged
...
}
}
EDIT
Here is a sample that seems to do what you've indicted about showing legacy data in the "SelectionBox", but not in the drop down list. To test this, try running it in KaXaml or something. Then start typing "Hello 3" and see that it suggests "Hello 30". This indicates that the Combo knows about the item. Now drop the list down and see that it isn't in the list. If you arrow down with the arrow keys, it skips from "Hello 20" to "Hello 40".
The next step would be to setup your templates so that the ListBoxItem template's Visibility is bound to "IsLegacy" on your NPR object. Then add both legacy and new items to the ItemsSource collection and bind to the list.
<ComboBox IsEditable="True">
<ComboBoxItem >Hello 10</ComboBoxItem>
<ComboBoxItem >Hello 20</ComboBoxItem>
<ComboBoxItem Visibility="Collapsed">Hello 30</ComboBoxItem>
<ComboBoxItem >Hello 40</ComboBoxItem>
</ComboBox>

WPF Validation Result Popup

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.

Resources