2

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?

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Aha - so was I on the right track thinking that it just doesn't know enough about Child, and is this a way of telling it what type to expect? With the attached property, it's still never specified from the Style, but I assumed that was part of the trouble. I was also assuming it would try and just fail if I applied the style when the child was NOT a Button (or didn't have IsPressed).
@JoshSutterfield The binding will silently fail if it can't find the property, and the trigger will never fire. If you want to be absolutely sure that only button's IsPressed is used for binding and nothing else, you can use this expression: (local:Attached.RelatedControl).(ButtonBase.IsPressed). If you want to support controls of multiple types, you can use PriorityBinding and list several bindings for every property.
What's surprising to me is that it can't find the property in cases where it's there. The equally fragile approach of using {Binding ElementName=childButton, Path=IsPressed} from a static resource happens to work. Ah well, yeah, it would've been a tradeoff of fragility for readability & having everything purely in XAML, but I'm glad I know the more robust way.
@JoshSutterfield Well, Child property isn't a dependency property, so issues with it aren't surprising.
Makes sense, that'd do it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.