WPF re-evaluate style triggers - wpf

My App supports Localization in English and Spanish. I have a textlabel that depending on the property Age it applies different styles.
For example, if Localization is set to English:
If Age < 18 -> Text = Under age [in colour red]
If Age > 18 -> Text = Over age [in colour green]
If Age == 18 -> Text = On eighteen [in colour blue]
This is working fine with this code:
<TextBlock Margin="5,0,0,0">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Age,
Converter={StaticResource CuttoffConverter}, ConverterParameter=18}"
Value="False">
<Setter Property="Text" Value="{Loc strUnderAge}"/>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Age,
Converter={StaticResource CuttoffConverter}, ConverterParameter=18}"
Value="True">
<Setter Property="Text" Value="{Loc strOverAge}"/>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding Age}" Value="18">
<Setter Property="Text" Value="{Loc strOnEighteen}"/>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
The localization part, is covered with {Loc XXXXXX} tag and it works fine except for this: if the language is changed "on-air", this DataTrigger Setter properties are not re-evaluated, so the labels are still displayed in English.
Is there any way to force to be re-evaluated?
UPDATE:
I have updated my Localization library to this one: WPF Localization Advanced which now supports Styles.
However, now it breakes when compiling the above XAML code. The error says:
"LocExtension is not valid for Setter.Value. the only supported
MarkupExtension types are DynamicResoruceExtension and BindingBase or
derived types."
Is there any way to accomplish the same purpose I did with trigger but with any of the suported methods by LocExtension?

define strUnderAge as a property within a class that Implements the INotifyPropertyChanged Interface.
bind the trigger to the strUnderAge property.
when you change the language raise the PropertyChanged event.
Otherwise the trigger can not know that the language has changed.

Related

How to modify legacy named style for having different setters based on targetTypes?

I have this named style
<Style x:Key="validationSupport" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="5,2,14,2" />
...OMISSIS...
<Style.Triggers>
...OMISSIS...
<DataTrigger Binding="{Binding DataContext.ActiveWorkspace.Editable, RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I use it extensively for TextBoxes, ComboBoxes, DatePickers etc, so I used as TargetType a super class for all these elements, Control.
Now I would like to differentiate the setter inside the dataTrigger using specific properties that 'Control' doesn't have. It seems I have to create different styles with different names,each for every targetType I want to differentiate, but that way I have to change the style name inside all elements which use it. Is there a smarter way to achieve that goal ? I don't want want to go and modify every xaml file I have.
Update after first answer
I have tried to put the following setters inside the datatrigger:
<Setter Property="Background" Value="#FFECECF8" />
<Setter Property="CheckBox.IsEnabled" Value="False" />
<Setter Property="DatePicker.IsEnabled" Value="False" />
<Setter Property="ComboBox.IsEnabled" Value="False" />
<Setter Property="TextBox.IsReadOnly" Value="True" />
Unfortunately the tests gave odd results. The IsEnabled property is set for TextBoxes too despite the prefix should limit its application to CheckBoxes, DatePickers and ComboBoxes.
My final need was to make some control contents unchangeable avoiding the difficult to read colors associated with disabled controls. From previous researches I understood that changing the colors for a 'disabled' control is not an easy task and involves the redefinition of the control template. So I thought to apply a combination of IsReadOnly and Background, but it is not applicable for the above problem. In fact CheckBoxes, DatePickers and ComboBoxes can only be made unchangeable using the IsEnabled property.
Am I missing something ?
There is a way, but I have to warn you - this is far from best-practice and should be avoided
WPF allows you to use desired type as a prefix for the property. That way, if you apply the style to a control that doesn't inherit from the prefixed type - the setter is ignored.
<Style x:Key="validationSupport" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="5,2,14,2" />
...OMISSIS...
<Style.Triggers>
...OMISSIS...
<DataTrigger Binding="{Binding DataContext.ActiveWorkspace.Editable, RelativeSource={RelativeSource AncestorType=Window}}" Value="False">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Button.Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
[Test this extensively, since I suspect that it might create memory leaks.]

How do I get simple editing on a Percent formatted field in WPF TextBox?

When you format a TextBox as currency and click to edit it, the $ and commas do not cause a problem, you can just edit and tab with no problem. When you format a field as a percentage things do not work so well.
<TextBox Text="{Binding CostMarkup,
StringFormat=P}"
Style="{StaticResource ctrlSpacingTight}" />
If the underlying value is 0.1 it correctly displays as 10%, if you go to edit it still shows as 10% the % will cause a problem plus it will change the underlying value from .1 to 10. I wrote a Converter to handle all this but I'm wondering if there isn't a better way. In particular is there a way to handle it the way currency handles it?
There is a built in currency converter so I suspect the currency version of StringFormat uses that. While there is a ZoomPerentageConverter it doesn't do what I would expect. Is there a way to hook in to StringFormat=P and have it invoke my Converter instead of having to go to every instance and explicitly specify it?
<TextBox Text="{Binding CostMarkup,
StringFormat=P,
Converter={StaticResource pctConverter}}"
Style="{StaticResource ctrlSpacingTight}" />
I've always found that it's easiest to display the raw data when editing, and the formatted value when not.
Here's an example that does that using a trigger
<Style x:Key="ctrlSpacingTight" TargetType="{x:Type TextBox}">
<!-- Other Style Setters -->
<Setter Property="Text" Value="{Binding CostMarkup, StringFormat={}{0:C}}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Text" Value="{Binding CostMarkup}" />
</Trigger>
</Style.Triggers>
</Style>
If ctrlSpacingTight is a global style, you can create a style for your TextBox that is BasedOn your global style.
<Style x:Key="CurrencyTextBox" TargetType="{x:Type TextBox}"
BasedOn="{StaticResource ctrlSpacingTight}">
<Setter Property="Text" Value="{Binding CostMarkup, StringFormat={}{0:C}}" />
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Text" Value="{Binding CostMarkup}" />
</Trigger>
</Style.Triggers>
</Style>

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.

How to define a default tooltip style for all Controls

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.

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

Resources