Reputation: 43
I'm trying to set the color of a rectangle (WPF) with triggers, depending on a boolean DependencyProperty, which I am binding to the Tag property of the rectangle.
I have the following code:
public partial class MainWindow : Window
{
public Boolean isAutoStart
{
get { return (Boolean)GetValue(isAutoStartProperty); }
set { SetValue(isAutoStartProperty, value); }
}
public static readonly DependencyProperty isAutoStartProperty =
DependencyProperty.Register("isAutoStart", typeof(Boolean),
typeof(MainWindow), new PropertyMetadata(true));
private void Window_Loaded(object sender, RoutedEventArgs e)
{
isAutoStart = false;
}
}
and in XAML:
<Window.Resources>
<Style x:Key="TriggerDark" TargetType="Rectangle">
<Setter Property="Fill" Value="Green" />
<Style.Triggers>
<Trigger Property="Tag" Value="False">
<Setter Property="Fill" Value="Red" />
</Trigger>
<Trigger Property="Tag" Value="True">
<Setter Property="Fill" Value="Green" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Rectangle Style="{StaticResource ResourceKey=TriggerDark}" Tag="{Binding Path=isAutoStart, UpdateSourceTrigger=PropertyChanged}">
If I hardcode "True" or "False" into the tag property of the rectangle, the triggers work correctly. And if I print the value of the tag property on runtime to console, the binding works, but the triggers do not fire.
Any ideas what I am doing wrong?
Thanks!
Upvotes: 2
Views: 1343
Reputation: 1864
You could try using a DataTrigger:
(Note that you can also semplify your style, removing a trigger)
<Style x:Key="TriggerDark" TargetType="Rectangle">
<Setter Property="Fill" Value="Green" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=isAutoStart}" Value="False">
<Setter Property="Fill" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
Setting also the datacontext to the window:
public MainWindow()
{
DataContext = this;
}
EDIT: If you want you can use a IValueConverter:
public class BoolToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
And change the binding to:
<Window.Resources>
<local:BoolToStringConverter x:Key="BtSConv"/>
<Style x:Key="TriggerDark" TargetType="Rectangle">
<Setter Property="Fill" Value="Green" />
<Style.Triggers>
<Trigger Property="Tag" Value="False">
<Setter Property="Fill" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Rectangle Style="{StaticResource TriggerDark}" Tag="{Binding isAutoStart, Converter={StaticResource BtSConv}}" />
Even there are a lot of beautiful solution for you from the others.
Upvotes: 1
Reputation: 128061
User nkoniishvt has already given the explanation, the Trigger compares the Tag
property value to the string literals "True" and "False" instead of the bool
values.
An alternative workaround for this problem may be not to use the Tag
property, but a properly typed attached property, declared e.g. as
public static class StyleHelper
{
public static readonly DependencyProperty StateProperty =
DependencyProperty.RegisterAttached(
"State", typeof(bool), typeof(StyleHelper));
public static bool GetState(DependencyObject obj)
{
return (bool)obj.GetValue(StateProperty);
}
public static void SetState(DependencyObject obj, bool value)
{
obj.SetValue(StateProperty, value);
}
}
You would use it in XAML like this:
<Window.Resources>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="Green" />
<Style.Triggers>
<Trigger Property="local:StyleHelper.State" Value="False">
<Setter Property="Fill" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Rectangle local:StyleHelper.State="{Binding isAutoStart}}" />
Upvotes: 1
Reputation: 132558
You need to set the DataContext
of your binding. Right now, your binding is pointing to to Rectangle.DataContext.isAutoStart
, however Rectangle.DataContext
is null, so your binding is not resolving into anything. See this answer for more details on the DataContext
.
Because you mentioned here you don't want to hardcode with a DataTrigger
, you probably will want to manually set the Source
of your binding to search the visual tree to the first Window
object instead, and bind to the isAutoStart
property of that.
<Rectangle Tag="{Binding Path=isAutoStart, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" .. />
You also have the extra problem as pointed out by this answer where you are comparing a boolean to a string, so it is always evaluating as False. There's many ways around this, such as an IValueConverter
, but I find the simplest is to import the System Namespace and create a Static value for boolean true, as shown here :
<s:Boolean x:Key="TrueValue">True</s:Boolean>
...
<Trigger Property="Tag" Value="{StaticResource TrueValue}">
where the s
namespace is defined as
xmlns:s="clr-namespace:System;assembly=mscorlib"
Upvotes: 1
Reputation: 2521
Your trigger tries to compare the boolean true with the string "True" because Tag is an Object property so the boolean will be stored whereas the value of your Trigger is a String. PHP would like it, not WPF. ;)
If you want to keep the Trigger instead of a DataTrigger you can create a static class:
public static class BooleanHelper {
public static bool False {
get { return false; }
}
public static bool True {
get { return true; }
}
}
And then the Style will be written as:
<Style x:Key="TriggerDark" TargetType="Rectangle">
<Setter Property="Fill" Value="Green"/>
<Style.Triggers>
<Trigger Property="Tag" Value="{x:Static local:BooleanHelper.True}">
<Setter Property="Fill" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
Credit to Michael Mairegger on the idea.
Upvotes: 2