wpfadorner

Setting a local style for an adorner


I have an adorner which should be placed beside it's adorned element. Depening on the value of the custom Position dependency property the adorner appears at the left or right side of the element.

I want to use a style to set the value of the Position property. But I can only do this if I add the style to the resources of the top-level control. If I place the style inside the resources of any child element it shows no effect.

Is there a way that I can set the adorner style on a per-element basis like in the following example?

<Window x:Class="StyledAdorner.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StyledAdorner">
    <Window.Resources>
        <Style TargetType="local:MyAdorner">
            <Setter Property="Position" Value="Right" />
        </Style>
        <Style TargetType="Button">
            <Setter Property="Content" Value="Adorn me!" />
            <Setter Property="Margin" Value="15" />
            <EventSetter Event="Click" Handler="AddAdorner" />
        </Style>
    </Window.Resources>
    <StackPanel>
        <Button />
        <Button>
            <Button.Resources>
                <Style TargetType="local:MyAdorner">
                    <!-- This setter has no effect! -->
                    <Setter Property="Position" Value="Left" />
                </Style>
            </Button.Resources>
        </Button>
    </StackPanel>
</Window>

The only solution I can image is to scan the adorned element's resources for an adorner style. If there is one then check if there is a setter for the Position property and use this value. But that looks like a really dirty hack...


private void AddAdorner(object sender, RoutedEventArgs e)
{
    new MyAdorner((UIElement)sender);
}
private Path _indicator = new Path { /* details omitted */ };

public MyAdorner(UIElement adornedElement) : base(adornedElement)
{
    AdornerLayer.GetAdornerLayer(AdornedElement).Add(this);
    AddVisualChild(_indicator);
    InvalidateMeasure();
    InvalidateArrange();
}

Solution

  • I could solve my problem by turning the Position property into an attached property:

    public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached(
        "Position",
        typeof(AdornerPosition),
        typeof(MyAdorner),
        new FrameworkPropertyMetadata(AdornerPosition.Right, UpdateAdornerLayerLayout));
    

    Now, I just have to set the desired value on the adorned element:

    <Button local:MyAdorner.Position="Left">
    

    The property is evaluated in adorner`s ArrangeOverride method when the position for the adorner is calculated.

    Note that the UpdateAdornerLayerLayout property changed callback is neccessary to force a layout update for the adorner layer when the property changes:

    private static void UpdateAdornerLayerLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is UIElement element)
        {
            var layer = AdornerLayer.GetAdornerLayer(element);
            layer?.Update();
        }
    }