Search code examples
.netwpfbindingstylesdatatrigger

How would you bind a DataTrigger to a Child property from a resource-defined Style?


How can I style an element based on changes in child properties? I would accept a different approach, but the desired result is simply that the parent property is set a certain way as long as a a child property (IsPressed) is true.

If we are styling one named item with an in-line style we can simply identify our child using ElementName, but we can even be less specific and use Child.IsPressed:

<Border Name="parentBorder" Padding="20">
    <Button Name="childButton" Content="Don't Push Me Bro"/>
    <Border.Style>
        <!--Now how can I do this from a static resource where I don't know the elementname?-->
        <Style TargetType="Border">
            <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Child.IsPressed}" Value="True">
                     <Setter Property="Background" Value="Blue"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Border.Style>
</Border>

But since I want to reuse the style and style several Borders I would like to move it to a resource, which is identical, except of course we are making a presumption that Child will always be a Button:

<Style x:Key="ButtonBorder" TargetType="{x:Type Border}">
    <Style.Triggers>                
        <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Child.IsPressed}" Value="True">
            <Setter Property="Background" Value="Blue"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

What is the best approach, or what am I missing? I can see that when "Child" is used as an inline style, intellisense colors Child differently and I'm sure that means something. Is assuming things about the type of Child from resource-defined styles just no good?


Solution

  • You can use attached properties for this. For example:

    public static class Attached
    {
        public static readonly DependencyProperty RelatedControlProperty = DependencyProperty.RegisterAttached(
            "RelatedControl", typeof(UIElement), typeof(Attached));
    
        public static void SetRelatedControl (DependencyObject element, UIElement value)
        {
            element.SetValue(RelatedControlProperty, value);
        }
    
        public static UIElement GetRelatedControl (DependencyObject element)
        {
            return (UIElement)element.GetValue(RelatedControlProperty);
        }
    }
    

    This property has no logic, it's just used for "connecting" controls any way you like.

    In the style, you read the attached property of your control:

    <Style x:Key="ButtonBorder" TargetType="{x:Type Border}">
        <Style.Triggers>
            <DataTrigger Binding="{Binding 
                    RelativeSource={RelativeSource Self}, 
                    Path=(local:Attached.RelatedControl).IsPressed}" Value="True">
                <Setter Property="Background" Value="Blue"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
    

    And when you create your control, you set the attached property to the child control:

    <Border Name="parentBorder" Padding="20" Style="{StaticResource ButtonBorder}"
            local:Attached.RelatedControl="{x:Reference childButton}">
        <Button Name="childButton" Content="Don't Push Me Bro"/>
    </Border>
    

    I wouldn't recommend using Child property, because it makes your design fragile. You make an assumption that a control will contain exactly one control and only directly. If you put a button inside a grid inside a border, it'll stop working. Detaching style relation from logical child relation will make the style more flexible.

    If what you actually want is to use a border style only for borders directly containing buttons, maybe you should just override button's template.