How to define a default tooltip style for all Controls - wpf

I would like to define a style with a template when there are validation errors and would display the first error message as a tooltip.
It works fine when targeting specific control like DatePicker in the following xaml.
<Style TargetType="{x:Type ToolKit:DatePicker}">
<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>
I cannot get it to work for Control though, i.e. the following doesn't give any tooltip
<Style TargetType="{x:Type ToolKit:Control}">
<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>
Any idea?

I would recommend you create a Behavior for this one.
Every control is unique in itself and this is not a reliable way to attach a specific behavior to all controls. And in fact, you may end up setting this property on unwanted controls that don't even require validation.
Behaviors are clean and you can assign them to selected controls only. Attached Properties or a Master Behavior can be used to assign Behaviors to child controls.
Here is a CodeProject article from Josh Smith to get you started on Behaviors.

Related

XAML Style Trigger - Change Style ONLY for Object of with a specific Name

I am new XAML however I am given the task to override some styles for certain elements within an existing application.
In my custom Theme, I am attempting to override the style of a BORDER control.
From what I can tell (using Snoop) to inspect the application, the element I want to change is just a plain border.
The border also seems to have a Name of "SubMenuBorder". Please see the image below.
Here is the latest iteration of my style snippet in which I am trying to set the border control's Background, BorderBrush and BorderThickness BUT ONLY if the control has a name of "SubMenuBorder"
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<Trigger Property="Name" Value="SubMenuBorder">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Unfortunately the above does NOT work.
The style trigger does not seem to fire/apply to the intended control.
If I simplify things further and just style ALL borders with the following snippet, then it seems to work and the border control I want to change, is styled, but so is every other border control in the application.
<Style TargetType="{x:Type Border}">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</Style>
Further Findings
I attempted to use a DataTrigger... which unfortunately doesn't work either.
Snoop shows below that the data trigger is being satisfied, however on the second image below you can see that the property of the background and borderbrush are still from the parenttemplate.
Any ideas please?
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Name}" Value="SubMenuBorder">
<Setter Property="Background" Value="Red"></Setter>
<Setter Property="BorderBrush" Value="Red"></Setter>
<Setter Property="BorderThickness" Value="20"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
You cannot use triggers to modify a Border that is defined in a ControlTemplate, with the exception of using an implicit Style that applies to all elements of the type specified by the TargetType property of the implicit Style.
You will either have to modify the ControlTemplate itself, or programmatically find the Border element in the visual tree and then change its runtime property values. The first approach, i.e. modifying or creating a custom template, is the recommended approach.
The name "SubMenuBorder" is only known and applicable within that Border element's namescope.

Why WPF Triggers must be declared into a style (even in-line)?

I don't understand why WPF allows me to write both
<Grid>
<Grid.Triggers>
<DataTrigger Binding="{Binding HasNeverBeenSeen}" Value="true">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Grid.Triggers>
</Grid>
and
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding HasNeverBeenSeen}" Value="true">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
but only the second seems to work. Why is there a Triggers tag to Grid element if we must use a Style?
Thanks
Short answer to your question is because this is how it is designed by WPF team.
FrameworkElement.Triggers can only have EventTriggers although property is collection of TriggerBase. It's also clearly stated on MSDN page:
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.

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

Redundant converter calls when using Triggers to determine value

I noticed that my XAML markup is wasting resources by doing conversions which it is not supposed to do. e.g. i have the following Style which acts as a switch:
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsDownloaded}" Value="True">
<Setter Property="Source"
Value="{Binding Data, Converter={StaticResource ByteArrayToBitmapImageConv}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsDownloaded}" Value="False">
<Setter Property="Source"
Value="{Binding Url, Converter={StaticResource UrlToBitmapImageConv}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Obviously this should either download an image if it has not been cached or turn the raw data into a BitmapImage. The problem is that as soon as both cases have taken place at least once both converters are called when the DataContext changes, irrespectively of the value that IsDownloaded has. So it will either display the converted image but still download it independendly in the background or it will download the image and try to convert null (the data) to a BitmapImage.
Setting the binding mode to OneTime did not help sadly.
I am looking for a clean way to avoid this as it even occurs multiple times in my application. e.g. here:
<Style TargetType="ContentControl">
<Setter Property="Content" Value="{StaticResource ContentNothingSelected}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={x:Static local:App.Settings}, Path=DisplayMode_Current}"
Value="Description">
<Setter Property="Content" Value="{StaticResource DescriptionViewer}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static local:App.Settings}, Path=DisplayMode_Current}"
Value="WebPage">
<Setter Property="Content" Value="{StaticResource WebPageViewer}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Source={x:Static local:App.Settings}, Path=DisplayMode_Current}"
Value="Media">
<Setter Property="Content" Value="{StaticResource MediaViewer}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Even if the display mode is set to Media or Description the application will navigate to the corresponding site in the background, wasting resources and throwing occasional out of place Javascript-error notifications.
I previously did most of this in code but i remodelled it to be more declarative and i would like to keep it that way, any help would be appreciated.
You could reconsider using Triggers and instead use a custom control and the Visual State Manager. Put the logic of downloading and caching and converting images in the custom control and simply set the appropriate states. It's hard to give an example without more xaml I'm afriad.

Resources