Get ValidationError in specific xaml element using a template - wpf

i have the following code snippet:
<ContentControl Height="16">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=txtDistanceH, Path=(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding ElementName=txtDistanceH, Path=(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
Now i want to put the style in a separate file instead of inline. However i would like to be able to specify which element it should get the Validation.Errors from, so i can use a single template for several different controls.
Is there any way to tell the template where it should get the Validation.Errors from, OTHER than binding to an element by name?
I tried setting the ContentControls DataContext to the element txtDistanceH, but then i just get a binding error saying that the property cannot be found on the root-element.

thanks for taking the time to answer my question. I've tried it and it works!
However i do have a comment and another related question.
The code i have now is:
<!-- Set content of contentcontrol to the ValidationError of a control stored in Tag, if there is one -->
<Style x:Key="ShowValidationError" TargetType="ContentControl">
<Style.Resources>
<x:Static x:Key="EmptyString" Member="System:String.Empty" />
</Style.Resources>
<Setter Property="Content" Value="{StaticResource EmptyString}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors).CurrentItem}" />
</DataTrigger>
</Style.Triggers>
</Style>
(Validation.Errors).CurrentItem is better than (Validation.Errors)[0], because the latter gives an out of range exception in the debug window when the error is resolved, see This Link for more information. The empty string ensures the control has the same size when its empty as when it has an error.
However even though it compiles and works, i still get some errors during design time. The code responsible is (Validation.HasError) and (Validation.Errors), respectively, in the above snippet.
Property 'Errors' is not attachable to elements of type 'Object'.
The property 'HasError' was not found in type 'Validation'.
Is there any way to fix / suppress these errors?

Bind the Tag property of the ContentControl to the target element using the element name binding and then update the style to use relative source self bindings to the tag to get at the validation errors.
Somewhere in Resources:
<Style x:Key=“ValidationStyle” TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.HasError)}" Value="True">
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag.(Validation.Errors)[0]}" />
</DataTrigger>
</Style.Triggers>
</Style>
And use it thusly:
<ContentControl Style=“{StaticResource ValidationStyle}” Tag=“{Binding ElementName=txtDistanceH}” />

Related

Trigger for IsChecked does not Trigger

I want to change the Icon of a ToggleButton (Content of Fluent RibbonBar) Depending on it's IsChecked Property. Now I have written the following style snippet:
<Fluent:ToggleButton.Style>
<Style BasedOn="{StaticResource RibbonButtonStyle}" TargetType="{x:Type Fluent:ToggleButton}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Fluent:ToggleButton}}, Path=IsChecked}" Value="False">
<Setter Property="Icon" Value="{StaticResource ResourceKey=Style.Images.Pined}"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Fluent:ToggleButton}}, Path=IsChecked}" Value="True">
<Setter Property="Icon" Value="{StaticResource ResourceKey=Style.Images.Unpined}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Fluent:ToggleButton.Style>
The problem is that the Trigger doesn't load the Image well. The problem is not, that IsChecked doesn't actualize itself, I've already tested this. And also I don't set the icon property anywhere else. The image resources also works fine if I use them otherwhere.
Information for Rebuild: I've put the ToggleButton into the Backstage as the DataTemplate of a RibbonListBox placed in a BackstageTabItem.
Since this is a style for togglebutton you don't need a findancestor binding - you would use self. And actually since IsChecked is a DP you can just use a trigger - eg <Trigger Property="IsChecked" Value="False">
You absolutely need to remove the Icon property from the control declaration as it will override everything a style tries to do due to precedence.
But those DataTriggers will not work, the binding will look for an ancestor, itself excluded, so you must change them as well as already pointed out by AndrewS. Only if both conditions are met you have a chance of getting this to work (there may be even additional interferences though).

Display validation error in DataGridCell tooltip

I have a WPF DataGrid which displays types that implement IDataErrorInfo. As expected when the validation fails the row gets the red exclamation mark and the invalid cell gets the red highlight.
This is all well and good; however, I want the validation error message to display in the tooltip of the invalid cell so the user has some indication of what is wrong. I presently have:
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors[0].ErrorContent}"/>
</Style>
</DataGrid.CellStyle>
This approach works for TextBox but not for DataGridCell. What is the difference?
I have something similiar in a project I'm working on right now, and it goes something like this:
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="DataGridCell.ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Style>
</DataGridTextColumn.ElementStyle>
Take a look at this MSDN log post:
https://blogs.msdn.microsoft.com/bethmassi/2008/06/27/displaying-data-validation-messages-in-wpf/
Follow its instructions to create a textbox cell editing template that will look something like this:
<Style TargetType="TextBox" x:Key="errTemplate">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Then, you can use it in your datagrid by setting the EditingElementStyle like so:
<DataGridTextColumn Header="Variable"
Binding="{Binding Variable, ValidatesOnDataErrors=True}"
EditingElementStyle="{StaticResource errTemplate}"/>
It is important to use the data trigger so that you can support a standard tool tip as well as a tool tip when there is an error as explained in this post:
Tooltip Not Showing Up When No Validation Error WPF

Why can't I add a DataTrigger to my control's Triggers collection?

Why cant I code like this
<Border Width="130" Height="70">
<Border.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorder}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<Setter Property="Style" Value="{StaticResource ResourceKey=ListBoxItemBorderInactive}"/>
</DataTrigger>
</Border.Triggers>
</Border>
I get this error
Failed object initialization (ISupportInitialize.EndInit).
Triggers collection members must be of type EventTrigger.
Error at object '4_T' in markup file
What am I doing wrong plz help.
Abe is correct and explains the limitations well. One thing you might want to consider is:
Instead of having two border styles, and trying to pick between them based on a trigger...
Use a single style on your border, this style's setters represent your 'normal' look.
This style also contains your DataTrigger, and your DataTrigger has a collection of setters which essentially represents your second style (which have higher priority than the standard setters when this trigger evaluates to true!
Edit:
Something like this -
<Style TargetType="Border" x:Key="BorderStyle">
<!-- These setters are the same as your normal style when none of your triggers are true -->
<Setter Property="BorderBrush" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="0">
<!-- These setters are the same as your ListBoxItemBorder style -->
<Setter Property="BorderBrush" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=CurrentStatus}" Value="200">
<!-- These setters are the same as your ListBoxItemBorderInactive style -->
<Setter Property="BorderBrush" Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
Unfortunately, only EventTriggers can be applied directly to elements. If you want to use a Trigger or DataTrigger, they have to be in a Style, ControlTemplate, or DataTemplate.
From the resource names, it looks like this is a Border inside a ListBoxItem ControlTemplate. You could easily move the triggers into the template's triggers collection.
Here is a way for no limitations triggers.
Example:
<Border Width="130" Height="100" Grid.Row="1">
<ListBox x:Name="lstItems" ItemsSource="{Binding TestItems}">
</ListBox>
<tg:TriggerExtensions.Triggers>
<tg:TriggerCollections>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxRed}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
<tg:DataTriggerInfo Binding="{Binding CurrentStatus}" Value="0" IsInvert="True">
<tg:DataTriggerInfo.Setters>
<tg:SetterInfo ElementName="lstItems" Property="Style" Value="{StaticResource ListBoxBlue}"/>
</tg:DataTriggerInfo.Setters>
</tg:DataTriggerInfo>
</tg:TriggerCollections>
</tg:TriggerExtensions.Triggers>
</Border>
Link Sample
Link Component Github

Conditionals in WPF

I have the following question: I have a Boolean variable in a configuration file. If it is true I want a property in textbox control to be setup according to the value of that variable.
Try the solution above but it does not work. What am I doing wrong?
This is a fragment code:
bool isKeyboardAvtive = true; //read from configuration file
<Style x:Key="StylesTextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=isKeyboardActive}" Value="True">
<Setter Property="k:TouchScreenKeyboard.TouchScreenKeyboard" Value="True"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=isKyboardActive}" Value="False">
<Setter Property="k:TouchScreenKeyboard.TouchScreenKeyboard" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox Style="{StaticResource StylesTextBox}" Margin="0,5" x:Name="txtUserName" Height="40" Width="150" />
IsKeyboardActive needs to be a public property of the DataContext for the binding to work. Also, you don't need a trigger there, just a binding :
k:TouchScreenKeyboard.TouchScreenKeyboard="{Binding IsKeyBoardActive}"
If you use the standard VS-generated settings, you can also bind to the settings directly :
xmlns:prop="clr-namespace:YourApplication.Properties"
...
k:TouchScreenKeyboard.TouchScreenKeyboard="{Binding IsKeyBoardActive, Source={x:Static prop:Settings.Default}}"
Or even better, using this markup extension :
xmlns:local="clr-namespace:YourApplication"
...
k:TouchScreenKeyboard.TouchScreenKeyboard="{local:SettingBinding IsKeyBoardActive}"

WPF DataGrid Validation error not being caught

I am trying to use exception validation on a cell in a DataGrid together with a style on the DataGridTextColumn's EditingElementStyle to set a tooltip with the content of the error. The error occurs but is not being caught or displayed within WPF.
The code and exception are shown below. Can someone tell me what I need to fix this?
Cheers,
Berryl
Here's the exception:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=Allocations[6].Amount; DataItem='ActivityViewModel' (HashCode=-938045583);
target element is 'TextBox' (Name='');
target property is 'Text' (type 'String')
TargetInvocationException:'System.Reflection.TargetInvocationException:
Exception has been thrown by the target of an invocation. --->
Domain.Core.PreconditionException: An allocation must be less than one day.
Here is the xaml for the DataGridTextColumn:
<dg:DataGridTextColumn
....
EditingElementStyle="{StaticResource cellEditStyle}"
Binding="{Binding Allocations[6].Amount, Converter={StaticResource amtConv},
ValidatesOnExceptions=True}"
/>
And here is the style that should provide Tooltip feedback on he error:
<Style x:Key="cellEditStyle" TargetType="{x:Type TextBox}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Padding" Value="0"/>
<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>
It's probably a bit late, but since I'm running into the same kind of trouble, here is a work-around for further reference (tested with .NET 4.0.30319).
1) Catching the exception
While the following binding code in the original post works fine with a TextBox, for example, it doesn't with a DataGrid text cell (even though the Msn documentation states so):
<!-- Doesn't work -->
<DataGridTextColumn Binding="{Binding Path=Age, ValidatesOnExceptions=True}"
...
/>
You have to add this bit:
<DataGridTextColumn Binding="{Binding Path=Age, Mode=TwoWay, ValidatesOnExceptions=True}"
...
/>
Note that, strangely enough (to me anyway), the exception will be catched and shown with the exclamation mark in the row header. You just won't have the red border nor the possibility to apply a style without the Mode=TwoWay part.
2) Applying a style
Another difficulty is setting a style in case of error, because the editing element will close as soon as you start the validation process. So attaching a style with:
<!-- Doesn't work -->
<DataGridTextColumn Binding="{Binding Path=Age, Mode=TwoWay, ValidatesOnExceptions=True}"
EditingElementStyle="{StaticResource datagridTBStyle}"
...
/>
will simply not work if you want to trigger on a validation error. Likewise with a CellStyle which will not have the error flag to trig on. You have to use a trick and declare a FrameworkElement style, like this:
<DataGridTextColumn Binding="{Binding Path=Age, Mode=TwoWay, ValidatesOnExceptions=True}"
ElementStyle="{StaticResource datagridElemStyle}"
...
/>
Good news is you can define the style on a derived element, like a TextBlock, and benefit from their properties:
<Style x:Key="datagridElemStyle" TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Yellow" />
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>

Resources