WPF DataBinding and Validation - wpf

I have a WPF User Interface and I've implemented bound ValidationRules with UpdateSourceTrigger="Explicit". I'm finding that it works perfectly when I have the tab page where the fields being validated is visible:
but if I'm on the third tab page where the form ends and trigger the UpdateSources method on the BindingExpressions for the fields being Validated then when I step back to the first tab the fields aren't actually highlighted in red. Even once I'm back on that tab page and re-trigger they don't highlight even though they're empty. I need to enter a value, re-trigger, then delete the value and re-trigger, THEN the modified ones get highlighted.
Anyone know how to address this behaviour?
Also is there a way to force a specific tab page to be switched to when the Validation fails, switching to the page which has the first field to fail?
I know there's an option to go with PropertyChanged and LostFocus but I'd like to get full form entry first and do all validation in one hit at the end.

It's because of the way the TabControl handles the AdornerLayer. They are obviously not preserved when the TabItem is switched. It works when you force to re-render the AdornerLayer when switching back to the TabItem.
<!-- Custom error template -->
<ControlTemplate x:Key="ValidationErrorTemplate">
<StackPanel>
<!-- Placeholder for the TextBox itself -->
<AdornedElementPlaceholder />
<TextBlock Text="{Binding ErrorContent}"
Foreground="Red" />
</StackPanel>
</ControlTemplate>
<!-- Style to trigger the rendering of the AdornerLayer by setting the error template -->
<Style TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate"
Value="{x:Null}" />
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Validation.HasError"
Value="True" />
<Condition Property="IsVisible"
Value="True" />
</MultiTrigger.Conditions>
<Setter Property="Validation.ErrorTemplate"
Value="{StaticResource ValidationErrorTemplate}" />
</MultiTrigger>
</Style.Triggers>
</Style>
Consider to disallow leaving the current tab if it has validation errors to avoid this behavior.

Related

Adding an ErrorTemplate to a WPF User Control disables TextBox input

I have a very basic custom control consisting of a Label and a Textbox. I've used my control for sometime without any issues.
I've now come to styling my application and have my style inside a XAML file containing just a ResourceDictionary. I have the following for my UserControl:
<Style TargetType="local:LabelEdit">
<Setter Property="Background" Value="{StaticResource BackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderBrush}" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder />
<Image Source="/AJSoft.Controls;component/Resources/Icons/cross.ico" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Foreground" Value="{StaticResource ErrorForegroundBrush}" />
<Setter Property="Background" Value="{StaticResource ErrorBackgroundBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource ErrorBorderBrush}" />
</Trigger>
</Style.Triggers>
</Style>
Everything works absolutely fine if I comment out the Setter for Validation.ErrorTemplate. If the ErrorTemplate is left intact, the cross shows (I haven't sorted out placement yet, but that can come later...), but the Textbox component of my UserControl does not show the caret or accept keyboard input. The rest of the controls in my Window work as expected.
Here are some screenies where I've deliberately put in some erroneous text to show how it looks.
The same problem happens even if I change that huge image to be a textblock with a small red "!" - the image is just for effect for now.
What am I doing that's causing the problem? I'm new to Validation in WPF...
EDIT: The image shown (big red cross) is just one example of what I've done. Even if I use a small image shown alongside the UserControl, I still get the same effect.
If you were to look at how error templates usually work, you'd see they apply to a single control.
Part of the issue you have here is you've got a label and textbox in one parent user control.
If you then apply an error template at the usercontrol level, it's on everything in that. Label, textbox, everything in your usercontrol.
The next thing to consider is how your error template ends up visible on top of everything. This happens because your error template generates UI in the adorner layer. That's top of everything ( in the window ).
Add these together and you got a big image on top of the content of your usercontrol.
At risk of over simplifying:
You put a top on your box and you can't now get at what's in that box.
There are several ways you could "fix" this but they all involve some design change or compromise.
Maybe a big X on top of your input control isn't a good idea.
You could kind of make it work.
You could make your image IsHitTestVisible="False".
It'll be visually in the way but you can then likely click on the textbox and type.
Just maybe not see everything.
Probably not ideal.
You could show your cross next to the textbox using a datatrigger rather than error template.
Add an image to your usercontrol so you have label, textbox, CrossImage.
Add a style to that with a setter makes it visible collapsed by default.
Use a trigger in that style to show the CrossImage when the control has errors.
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter ... />
</Trigger>
</Style.Triggers>
You may well find it simplest to use the tag on the usercontrol and set that to visible/collapsed. Bind visibility of the image to that.

focus is wrong when trying to click inside a RadComboBox

So i have a RadCombobBox which is heavilly edited to have a list of options where 1 of the options can be an input field. There are different types of input fields you can chose from but i'm having a problem with the integer input field. There's also one with a text input field which shows no problems.
The combobox looks like this:
The text is dutch (don't mind it) beneath the text options there is a preset value, the idea here is you can either choose a preset setting (which corresponds to an integer value) or you can type in your own value.
What happens when i try to click on different places (last one is very peculiar):
If i try to click on the input box (which is highlighted in gray) it selects it and closes the combo box (this is not what i want).
If i try to click on the arrows on the right side of the combobox it changes the value.
If i scroll with my mouse, it changes the value.
If i keep my mouse down on the input box i can actually type in the value i want.
If i first click on the buttons on the side and then click inside the input box i can edit the value.
What I want is to click on the input box and be able to edit the value inside and when i'm finished press enter (or outside the combobox) to close it.
Somehow the focus or, something (i am not 100% sure) just seems to fail me.
here is the xaml code for the IntegerDataTemplate:
Integer selector
<Style x:Key="NumericUpDownStyle" TargetType="{x:Type telerik:RadNumericUpDown}">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
</Trigger>
<Trigger Property="IsEditable" Value="False">
<Setter Property="SmallChange" Value="0" />
<Setter Property="LargeChange" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
<!-- Integer editor -->
<DataTemplate x:Key="IntegerDataTemplate">
<telerik:RadNumericUpDown x:Name="NumericUpDown"
Width="{Binding Path=ActualWidth,
ElementName=Editor}"
MaxWidth="{Binding Path=ActualWidth,
ElementName=Editor,
Converter={StaticResource WidthToWidthConverter}}"
HorizontalContentAlignment="Left"
Background="Transparent"
FontFamily="{telerik:Windows8Resource ResourceKey=FontFamilyStrong}"
IsInteger="True"
Style="{StaticResource NumericUpDownStyle}"
UpdateValueEvent="PropertyChanged"
Value="{Binding Path=Value,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True}">
<telerik:RadNumericUpDown.NumberFormatInfo>
<globalization:NumberFormatInfo NumberGroupSeparator="" />
</telerik:RadNumericUpDown.NumberFormatInfo>
</telerik:RadNumericUpDown>
</DataTemplate>
<!-- Integer as Option -->
<DataTemplate x:Key="OptionsDataTemplate">
<TextBlock Height="20" Text="{Binding Converter={StaticResource IntegerSelectorObjectToStringConverter}}" />
</DataTemplate>
<local:SelectorTypeTemplateSelector x:Key="IntegerTemplateSelector"
OptionsDataTemplate="{StaticResource OptionsDataTemplate}"
SelectorDataTemplate="{StaticResource IntegerDataTemplate}" />
Somewhere it is going wrong, is there anyone that can point me to the right way to fix it (a work around would be fine as well).
I would like to add I have the same code but with a text box which does work properly, I've added the code below to have a comparison but i haven't been able to find a significant difference.
Text selector (which actually works properly)
<Style x:Key="TextBoxStyle" TargetType="{x:Type telerik:RadWatermarkTextBox}">
<Setter Property="BorderBrush" Value="{StaticResource BasicBrush}" />
<Setter Property="FontFamily" Value="{telerik:Windows8Resource ResourceKey=FontFamilyStrong}" />
<Setter Property="Padding" Value="2,2,0,0" />
<Setter Property="Validation.ErrorTemplate" Value="{StaticResource ErrorTemplate}" />
<Setter Property="telerik:RadWatermarkTextBox.WatermarkTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Margin="2,3,0,0"
FontFamily="Segoe UI"
FontStyle="Italic"
Foreground="{StaticResource WaterMarkBrushNoOpacity}"
Padding="0,-2,0,0"
Text="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="BorderBrush" Value="{StaticResource AccentBrush}" />
</Trigger>
<Trigger Property="IsReadOnly" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource MarkerDisabledBrush}" />
</Trigger>
</Style.Triggers>
</Style>
<!-- String editor -->
<DataTemplate x:Key="StringDataTemplate">
<telerik:RadWatermarkTextBox x:Name="WatermarkTextBox"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Top"
Background="Transparent"
BorderThickness="1"
Style="{StaticResource TextBoxStyle}"
Text="{Binding Path=Value,
UpdateSourceTrigger=PropertyChanged,
NotifyOnSourceUpdated=True}" />
</DataTemplate>
<!-- String as Option -->
<DataTemplate x:Key="OptionsDataTemplate">
<TextBlock Height="20" Text="{Binding Converter={StaticResource StringSelectorObjectToStringConverter}}" />
</DataTemplate>
<local:SelectorTypeTemplateSelector x:Key="StringTemplateSelector"
OptionsDataTemplate="{StaticResource OptionsDataTemplate}"
SelectorDataTemplate="{StaticResource StringDataTemplate}" />
It's really difficult to know what's going on without actually fiddling around with all the bits and pieces (e.g. the RadComboBox style + code), but it would be natural to assume that is has something to do with the drop down automatically closing when it loses focus, especially given that the standard WPF ComboBox has a FocusedDropDown state which opens the popup.
You could try and duplicate the RadComboBox by importing the style from Telerik, and extending the code behind into a new class (and restyle with the imported template). This way you can override methods and attach to events (e.g. Got/LostFocus), and play around with the template to see if you can adjust it to your needs.
However trying to wrestle such behavior into existing controls, just because it is possible to restyle the templates, often just ends up in a lot of grief (and hours wasted).
I have a feeling it would be easier to create a NumericRadComboBox which has the numeric up/down embedded in the combobox itself. So you restyle the RadComboBox to have numeric up/down buttons next to the drop down arrow, and implement the increment/decrement behavior manually.
The solution was rather simple, I was trying to fix it with preview mouse down but what i needed to do was on preview mouse up by adding: PreviewMouseLeftButtonUp="EditorPreviewMouseLeftButtonUp" in the xaml, which made the RadComboBox code as follows:
telerik:RadComboBox x:Name="Editor"
BorderThickness="0"
FontWeight="SemiBold"
ItemTemplateSelector="{StaticResource IntegerTemplateSelector}"
PreviewMouseLeftButtonUp="EditorPreviewMouseLeftButtonUp"
SelectionBoxTemplate="{StaticResource SelectionboxTemplate}"
Validation.ErrorTemplate="{StaticResource ErrorTemplate}" />
The code behind looked lik this:
private void EditorPreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var originalSource = e.OriginalSource as FrameworkElement;
if ((originalSource != null) && (originalSource.ParentOfType<TextBox>() != null))
{
e.Handled = true;
}
}
The MouseUp is when the selection changed event is called which causes the combobox to close, by stating the event is finished before that happens it won't close the combobox. All you now have to do is press enter after you've changed the value and it will be selected and updated correctly.

ContentControl change ContentTemplate on GotFocus

I have a UserControl which contains a ContentControl. When the user clicks this ContentControl I want to change its ContentTemplate, to make it "editable" (instead of labels display textboxes for example).
What I have is this:
<StackPanel>
<ContentControl Style="{DynamicResource ContainerStyleEditable}" GotFocus="ContentControl_GotFocus"></ContentControl>
</StackPanel>
and in userControl resources i have
<Style TargetType="{x:Type ContentControl}" x:Key="ContainerStyleEditable">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateReadOnly}" />
<Style.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter Property="ContentTemplate" Value="{DynamicResource ItemTemplateEditable}" />
</Trigger>
</Style.Triggers>
</Style>
This doe not work, it seems the GotFocus event never fires. What is the way to to this?
I usually base my triggers of IsKeyboardFocusWithin instead of IsFocused because often the focused element usually isn't the actual ContentControl, but rather a control inside it's Content.
Also, be sure that at least one control inside the ContentControl can accept focus so the control can get focus. If nothing inside the control can accept focus, your trigger will never fire.

How to redirect the Focus within a custom Control

I got a CustomControl inherit from : Control. It contains 2 Labels, 1 TextBox and 2 Buttons (simplyfied view)
<Button IsTabStop="False" x:Name="ParameterUpButton" IsEnabled="False" Width="14" Height="10">
<Button IsTabStop="False" x:Name="ParameterDownButton" IsEnabled="False" Width="14" Height="10"/>
<StackPanel Orientation="Horizontal" Height="28" VerticalAlignment="Center">
<Label Content="{TemplateBinding ParameterName}" Width="45" />
<TextBox IsEnabled="False" x:Name="ParameterValueTextBox" Text="{TemplateBinding ParameterValue}" Width="36" Height="23"/>
<Label Content="{TemplateBinding ParameterUnit}" Width="33" />
</StackPanel>
As the user should be able to navigate through some of these custom controls, i want to redirect the Focus by tabbing through my controls. Simply said:
I want to navigate through all these Textboxes, and only those (the buttons should be no tab stops!).
The Buttons and the Textbox are disabled by default, and the template got some triggers to enable them by IsMouseOver and by IsKeyboardFocused (to show a different style).
How can i focus the Textbox (and enable it including the two buttons)
using Tab to navigate through my controls?
by clicking on my control (no matter where, just within the control)
The usage looks like the following:
<!-- some more controls -->
<CustomControls:ParameterControl ParameterName="foo" ParameterUnit="x" ParameterValue="6" Margin="5"/>
<CustomControls:ParameterControl ParameterName="bar" ParameterUnit="y" ParameterValue="6" Margin="5"/>
<CustomControls:ParameterControl ParameterName="foobar" ParameterUnit="z" ParameterValue="6" Margin="5"/>
<!-- some more controls -->
Thanks for any help in advance!
Edit: I tried it by overriding the Control's OnGotFocus event like this:
protected override void OnGotFocus(RoutedEventArgs e)
{
var tb = ((TextBox) Template.FindName("ParameterValueTextBox", this));
tb.IsEnabled = true;
tb.Focus();
}
I also wrote a Trigger for my Control:
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition SourceName="ParameterValueTextBox" Property="IsKeyboardFocused" Value="true"/>
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsEnabled" TargetName="ParameterDownButton" Value="true"/>
<Setter Property="IsEnabled" TargetName="ParameterUpButton" Value="true"/>
<Setter Property="IsEnabled" TargetName="ParameterValueTextBox" Value="true"/>
</MultiTrigger.Setters>
</MultiTrigger>
Actually, this trigger works for the Buttons (i can see the style changing and changing back after losing the focus), but my TextBox is still being enabled (at least the style does not change back)
Meanwhile, i've found the solution myself. Might be interesting to somebody else:
As i've been juggling around with IsEnabled and setting the focus to the input field, i think there might be some problem in the order the triggers, styles and function calls in the Class of my Control. Anyway, i've solved it in the following way:
I removed these three setters
<MultiTrigger.Setters>
<Setter Property="IsEnabled" TargetName="ParameterDownButton" Value="true"/>
<Setter Property="IsEnabled" TargetName="ParameterUpButton" Value="true"/>
<Setter Property="IsEnabled" TargetName="ParameterValueTextBox" Value="true"/>
</MultiTrigger.Setters>
to
<MultiTrigger.Setters>
<Setter Property="Tag" TargetName="ParameterDownButton" Value="Enabled"/>
<Setter Property="Tag" TargetName="ParameterUpButton" Value="Enabled"/>
<Setter Property="Tag" TargetName="ParameterValueTextBox" Value="Enabled"/>
</MultiTrigger.Setters>
And enabled the buttons all the time. After having applied the style triggers to the Tag Property, everything looks the same and works perfectly. There might be a problem else to focus a disabled element (depends on the order the setters, styles and code within the Class itself are invoked).

WPF ComboBox - showing something different when no items are bound

I have a ComboBox, and i want to change its look when the ItemsSource property is null. When it is in that state, i want to show a TextPanel with the text "Retrieving data" in it, and give it a look kind of similar to the watermarked textbox.
I figure to do this i need a ControlTemplate, and a trigger. I have the ControlTemplate here:
<ControlTemplate x:Key="LoadingComboTemplate" TargetType="{x:Type ComboBox}">
<Grid>
<TextBlock x:Name="textBlock" Opacity="0.345" Text="Retrieving data..." Visibility="Hidden" />
</Grid>
<!--
<ControlTemplate.Triggers>
<Trigger Property="ComboBox.ItemsSource" Value="0">
<Setter Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
-->
</ControlTemplate>
but my issue is how do i set up the trigger to show this when the ItemsSource property is null? I have tried a couple of different ways, and each way has given me the error message "Value 'ItemsSource' cannot be assigned to property 'Property'. Invalid PropertyDescriptor value.". My ComboBox xaml is this (including the attempted trigger):
<ComboBox Margin="112,35,80,0"
Name="MyComboBox"
Height="22.723"
VerticalAlignment="Top"
DisplayMemberPath="FriendlyName"
SelectedValuePath="Path"
TabIndex="160"
>
<Trigger>
<Condition Property="ItemsSource" Value="0" />
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</ComboBox>
now should the trigger go on the ComboBox, or on the ControlTemplate? How do i access the ComboBox's ItemsSource property? Should i even be using a trigger?
Thanks!
Try putting {x:Null} for the value of the condition instead of 0.
Also I got it working by moving the Trigger to a style and modifing it slightly, see below.
<Style TargetType="ComboBox" x:Key="LoadingComboStyle">
<Style.Triggers>
<Trigger Property="ItemsSource" Value="{x:Null}">
<Setter Property="Template" Value="{StaticResource LoadingComboTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<ComboBox Style="{StaticResource LoadingComboStyle}" .... >
The reason it only works in a style, is that only EventTriggers are allowed in the triggers collection directly on the Framework Element. For property triggers (like above) you need to use a style (I learn something every day).
See FrameworkElement.Triggers
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.

Resources