Data trigger check value of dynamic resource - wpf

I have a WPF application and the content of should be displayed in German and English. Therefore, I created two seperate resource dictionaries which contain string snippets in each language. It is possible to switch between the languages while the application is running.
At this point I got stuck at a problem. There are some settings the user can make. If the setting was completed successfully a message shows up. The text of the message is taken from the resource dictionary. Based on a success or error message the text is displayed green or red.
<TextBlock Text="{Binding UpdateTaxPercentageSettingsMessage}" FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{DynamicResource tax_percentage_update_went_wrong}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{StaticResource active_tax_law_update_went_wrong}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{StaticResource tax_percentage_was_updated_successfully}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{StaticResource active_tax_law_was_updated_successfully}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{StaticResource differential_taxation_info_update_went_wrong}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding UpdateTaxPercentageSettingsMessage}" Value="{StaticResource differential_taxation_info_was_updated_successfully}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
To make switching languages possible while the application is running, the string snippets taken from the resource dictionary has to be a DynamicResource. Unfortunately, I can not use DynamicResources as condition in data triggers. Has anyone faced a similar problem yet? I am grateful for your suggestions!

Do not use triggers on localized text, neither does it work with DynamicResouce, because Value is not a dependency property, nor is it readable. Instead, create an enum that describes your errors.
public enum ErrorType
{
WhoCares, // No comment on this.
ThisIsSuspicious, // Suspicous value.
ItsATrap, // Admiral Ackbar warned us.
ItIsNotWhatYouThinkItIs, // It is exactly what you think.
ItCannotBeThatSerious, // Serious Sam approves.
WhatDoesTheFlashingRedLightMean // If it is still flashing, how bad can it be, really?
}
Expose anothe property for the error and implement INotifyPropertyChanged if necessary.
public ErrorType ErrorType { get; }
Use the property instead of your resources as Value of the triggers.
<TextBlock Text="{Binding UpdateTaxPercentageSettingsMessage}" FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.WhoCares}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.ThisIsSuspicious}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.ItsATrap}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.ItIsNotWhatYouThinkItIs}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.ItCannotBeThatSerious}">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding ErrorType}" Value="{x:Static local:ErrorType.WhatDoesTheFlashingRedLightMean}">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Bonus round. As your data triggers are repetitive (they set the same values often), consider using a Binding with a custom converter that simply says, if the bound value matches any of the given values, apply this setter.
public class IsMatchingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(parameter is IEnumerable enumerable))
return false;
return enumerable.Cast<object>().Contains(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Create an instance of the converter in any resource dictionary in scope.
<Window.Resources>
<local:IsMatchingConverter x:Key="IsMatchingConverter"/>
</Window.Resources>
Change the data triggers and pass the target values in an array as converter parameters.
<TextBlock Text="{Binding UpdateTaxPercentageSettingsMessage}" FontWeight="Bold">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="ErrorType" Converter="{StaticResource IsMatchingConverter}">
<Binding.ConverterParameter>
<x:Array Type="local:ErrorType">
<local:ErrorType>WhoCares</local:ErrorType>
<local:ErrorType>ThisIsSuspicious</local:ErrorType>
<local:ErrorType>ItCannotBeThatSerious</local:ErrorType>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Value="True">
<DataTrigger.Binding>
<Binding Path="ErrorType" Converter="{StaticResource IsMatchingConverter}">
<Binding.ConverterParameter>
<x:Array Type="local:ErrorType">
<local:ErrorType>ItsATrap</local:ErrorType>
<local:ErrorType>ItIsNotWhatYouThinkItIs</local:ErrorType>
<local:ErrorType>WhatDoesTheFlashingRedLightMean</local:ErrorType>
</x:Array>
</Binding.ConverterParameter>
</Binding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

Related

WPF pass ElementName as parameter into a Style

I have 3 TextBlocks whose visibility property depends on a validation rule on 3 different elements (TextBoxes). How can I refactor this code so that the ElementName is abstracted away and I just have one style defined instead of 3? Is it possible to pass in ElementName as a parameter at the TextBlocks which this style is applied to?
<Style x:Key="textBlock_stockPriceWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_taxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_capGainsTaxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
**Update: Per Mark's advice below, I tried using the following ControlTemplate and attached property definition:
<ControlTemplate x:Key="LabelWarning" TargetType="Label">
<Border BorderBrush="AliceBlue"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
public static class ElementNameHelper
{
public static readonly DependencyProperty ElementAliasProperty =
DependencyProperty.RegisterAttached("ElementAlias",
typeof(string), typeof(ElementNameHelper));
public static string GetElementAlias(DependencyObject obj)
{
return obj.GetValue(ElementAliasProperty).ToString();
}
public static void SetElementAlias(DependencyObject target, string value)
{
target.SetValue(ElementAliasProperty, value);
}
}
And invoking it like this:
<Label Content="Stock price should be a positive number."
FontSize="12" HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="Red"
Margin="1"
views:ElementNameHelper.ElementAlias="stockPriceTextBox"
Template="{StaticResource LabelWarning}"/>
But then the XAML compiler tells me that: A 'Binding' cannot be set on the 'ElementName' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject. Do I have this setup correctly and is it just the case that you simply can't use this approach on ElementName? Or am I missing something with the attached property definition?
Usual way to do this is with an attached property.
If you just want a cheap-n-easy solution then you can use Tag, which is described in the documentation as "an arbitrary object value that can be used to store custom information". An attached property is the better way to do it though.

wpf vb.net Datagrid image in row depending on value in cell

This is the code i use but it is notworking
<Window.Resources>
<Style x:Key="PinkRow" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Rank}" Value="Master">
<Setter Property="Source" Value="A_Cancel.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding RANK}" Value="Bosun">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding RANK}" Value="">
<Setter Property="Background" Value="yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
If field rank has the value "Master" i want the image A_cancel.png
The C# is case sensitive, so the Rank and RANK are different properties. Use proper property name in your bindings. Also you must mention in your Setter the TargetName for Image element.
<DataTrigger Binding="{Binding Rank}" Value="Master">
<Setter TargetName="ImageElementName" Property="Source" Value="A_Cancel.png"/>
</DataTrigger>

Why is this DataTrigger not working after a PropertyChanged?

The following triggers work almost as expected:
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>
After loading the view, the colors are correct.
Then I execute an async taks en when it has finished I give the propertychanged on the object that has the binding to my datagrid-row.
But why is the DataTrigger not fired (I have to refresh the view to see the effect)?
EDIT:
My problem is that I don't now which property I have to give the PropertyChanged.
Some details about the datagrid (Projects is an ObservableCollection):
DataGrid SelectedItem="{Binding Project}" ItemsSource="{Binding Projects}">
The property of object Project that the binding must use is:
Project.Variants[0].InUse
I tried also the triggers:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<DataTrigger Value="True" Binding="{Binding Path=., Converter={StaticResource InUseConverter}}">
In the view model I have tried after Project.Variants[0].InUse = null;:
Project.OnPropertyChanged("InUse");
Project.Variants[0].OnPropertyChanged("InUse");
raisePropertyChanged("Project.Variants[0].InUse");
raisePropertyChanged("Variants[0].InUse");
raisePropertyChanged("Projects");
raisePropertyChanged("Project");
raisePropertyChanged("InUse");
At last it works using:
<DataTrigger Value="True" Binding="{Binding Path=Variants[0].InUse, Converter={StaticResource NotNullConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
Project.Variants[0].OnPropertyChanged("InUse");
Try to set Foreground property in your style to change it dynamically at runtime
<Setter Property="Foreground" Value="White"/>
<Style.Triggers>
<Trigger Value="True" Property="IsSelected">
<Setter Property="Foreground" Value="White" />
</Trigger>
<DataTrigger Value="True" Binding="{Binding UpdateSourceTrigger=PropertyChanged, Converter={StaticResource InUseConverter}}">
<Setter Property="Foreground" Value="OrangeRed" />
</DataTrigger>
</Style.Triggers>

WPF ListView & Converter

I have a ListView with a GridViewColumn that has an image in it, it uses a converter to convert a bool to a ImageSource. It was working perfectly for months and now it suddenly just shows a red dot instead of my image.
Converter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
return new BitmapImage(new Uri(#"/Dionysus.Styling;component/Images/Actions-dialog-ok-apply-icon.png", UriKind.RelativeOrAbsolute));
else return
null;
}
Xaml:
<GridViewColumn Header="">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Path=IsDelayedOrPreferred, Converter={StaticResource DelayConverter}, Mode=TwoWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Result:
Has anyone seen this behaviour before or know what might have changed. The only recent changes that I made was upgrading to VS 2013 but all other ListViews with Converters
are still working as expected.
Style:
<Style TargetType="{x:Type ListView}">
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="AlternationCount" Value="2"/>
<Setter Property="Background" Value="WhiteSmoke"/>
<EventSetter Event="Loaded" Handler="ListView_Loaded"/>
</Style>
<Style TargetType="{x:Type GridViewColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Margin" Value="1,0"/>
</Style>
<Style TargetType="{x:Type ListViewItem}" x:Key="ListViewStyle">
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="WhiteSmoke"></Setter>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="White"></Setter>
</Trigger>
<DataTrigger Binding="{Binding IsFirst}" Value="True">
<Setter Property="Background" Value="LightGreen"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsLastUnpaid}" Value="True">
<Setter Property="Background" Value="LightSalmon"></Setter>
</DataTrigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#FF41B1E1"></Setter>
</Trigger>
</Style.Triggers>
<Setter Property="Height" Value="20" />
<Setter Property="HorizontalAlignment" Value="Left"/>
</Style>
UPDATE:
I recently changed from a ListView to a DataGrid and then I see a little exclamation mark as if there is data validation errors on the DataGrid.
Any ideas?

How to pass the binding path to DataTrigger in a WPF style?

I've several style with only a little difference in binding path:
<Style TargetType="FrameworkElement" x:Key="FieldValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=FieldValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="NumberValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=NumberValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="TextValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TextValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="DateTimeValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DateTimeValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="DateValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DateValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style TargetType="FrameworkElement" x:Key="TimeValidationErrorStyle">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TimeValidationError}" Value="">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
We can find the only difference between the styles is the {Binding Path=xyz} in DataTrigger, can I remove the duplication with only XMAL markup? I know we can create custom styles as in this question but the setters are hard coded - can we only extend DataTrigger?
You can use a single style with a MultiBinding DataTrigger and an OR Converter in it.
<Window.Resources>
<local:AtleastOneEmptyConverter x:Key="AtleastOneEmptyConverter"/>
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{StaticResource AtleastOneEmptyConverter}">
<Binding Path="FieldValidationError"/>
<Binding Path="NumberValidationError"/>
<Binding Path="TextValidationError"/>
...
<Binding Path="DateTimeValidationError"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
Code Behind:
public class AtleastOneEmptyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
return values.Cast<string>().Any(p => string.IsNullOrEmpty(p));
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I do suggest, to skip applying styles to all framework elements in your application. It can make your application GUI slow if it grows complex in the future and seek an alternate approach such as Validation Model of WPF.
If you still want to apply style to all types of framework elemetns in your application then you can alternately explore App.xaml way to override styles of all framework elements in your application...
Seems like you want to create a template for a DataTrigger. Since you can't style DataTriggers, you have to resort to creating your own:
public class CollapsingDataTrigger : DataTrigger
{
public CollapsingDataTrigger()
{
base.Setters.Add(new Setter(FrameworkElement.VisibilityProperty, Visibility.Collapsed));
}
}
Then update your XAML to use this CollapsingDataTrigger instead of the regular one(you will have to reference the namespace this is created in).

Resources