1

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!

H.B.
  • 166,899
  • 29
  • 327
  • 400
Niki
  • 33
  • 7
  • 1
    The Binding won't work unless you set a source object, e.g. by setting the Window's DataContext to itself, or specifying the Binding's RelativeSource property. Besides that, setting UpdateSourceTrigger doesn't make sense because it has no effect here. – Clemens Jul 07 '16 at 12:40
  • 1
    It may also make sense not to bind the Tag property and set a Trigger on its Value, but instead use a DataTrigger that binds directly to the isAutoStart property. – Clemens Jul 07 '16 at 12:44
  • In case you did not set the DataContext, try `Tag="{Binding isAutoStart, RelativeSource={RelativeSource AncestorType=Window}}"`. – Clemens Jul 07 '16 at 12:47
  • I've set the DataContext to `DataContext="{Binding RelativeSource={RelativeSource Self}}"`but it still does not work, also not with RelativeSource set. – Niki Jul 07 '16 at 13:17
  • You have to set relative source as said @Clemens in the previous messages.In this case, Self means (Rectangle and not Window) – Babbillumpa Jul 07 '16 at 13:26

4 Answers4

2

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.

nkoniishvt
  • 2,442
  • 1
  • 14
  • 29
1

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.

Babbillumpa
  • 1,854
  • 1
  • 16
  • 21
  • I don't want to bind the dependency property directly with a DataTrigger, since I want to reuse this "template" and just pass a different dependency property within the Tag property. – Niki Jul 07 '16 at 13:22
1

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"
Community
  • 1
  • 1
Rachel
  • 130,264
  • 66
  • 304
  • 490
1

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}}" />
Clemens
  • 123,504
  • 12
  • 155
  • 268