implementing-wpf-adorners

Adorner is a mechanism for overlaying decorative visual elements on top of UIElements.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "implementing-wpf-adorners" with this command: npx skills add christian289/dotnet-with-claudecode/christian289-dotnet-with-claudecode-implementing-wpf-adorners

WPF Adorner Patterns

Adorner is a mechanism for overlaying decorative visual elements on top of UIElements.

  1. Adorner Concept

1.1 Characteristics

  • AdornerLayer: Separate rendering layer that holds Adorners

  • Z-Order: Always renders above the adorned element

  • Layout Independent: No effect on target element's layout

  • Event Support: Can receive mouse/keyboard events

1.2 Usage Scenarios

Scenario Description

Validation Display Input field error display

Drag Handles Element move/resize handles

Watermark Hint text for empty TextBox

Selection Display Highlight selected elements

Tooltip/Badge Additional info display on elements

Drag and Drop Preview during drag

  1. Basic Adorner Implementation

2.1 Simple Adorner

namespace MyApp.Adorners;

using System.Windows; using System.Windows.Documents; using System.Windows.Media;

/// <summary> /// Adorner that draws border around element /// </summary> public sealed class BorderAdorner : Adorner { private readonly Pen _borderPen;

public BorderAdorner(UIElement adornedElement) : base(adornedElement)
{
    _borderPen = new Pen(Brushes.Red, 2)
    {
        DashStyle = DashStyles.Dash
    };
    _borderPen.Freeze();

    // Disable mouse events (decoration only)
    IsHitTestVisible = false;
}

protected override void OnRender(DrawingContext drawingContext)
{
    var rect = new Rect(AdornedElement.RenderSize);

    // Draw border
    drawingContext.DrawRectangle(null, _borderPen, rect);
}

}

2.2 Applying Adorner

// Get AdornerLayer var adornerLayer = AdornerLayer.GetAdornerLayer(targetElement);

if (adornerLayer is not null) { // Add Adorner var adorner = new BorderAdorner(targetElement); adornerLayer.Add(adorner); }

2.3 Removing Adorner

// Remove all Adorners from specific element var adornerLayer = AdornerLayer.GetAdornerLayer(targetElement); var adorners = adornerLayer?.GetAdorners(targetElement);

if (adorners is not null) { foreach (var adorner in adorners) { adornerLayer!.Remove(adorner); } }

  1. AdornerDecorator

3.1 Default Location

<!-- Window default template includes AdornerDecorator --> <Window> <!-- AdornerDecorator is automatically included --> <Grid> <TextBox x:Name="MyTextBox"/> </Grid> </Window>

3.2 Explicit AdornerDecorator

<!-- Explicit AdornerDecorator in ControlTemplate --> <ControlTemplate TargetType="{x:Type ContentControl}"> <AdornerDecorator> <ContentPresenter/> </AdornerDecorator> </ControlTemplate>

<!-- In Popup or special containers --> <Popup> <AdornerDecorator> <Border> <StackPanel> <TextBox/> <Button Content="OK"/> </StackPanel> </Border> </AdornerDecorator> </Popup>

  1. Practical Adorner Examples

4.1 Watermark Adorner

namespace MyApp.Adorners;

using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;

/// <summary> /// Display watermark (hint text) on TextBox /// </summary> public sealed class WatermarkAdorner : Adorner { private readonly TextBlock _watermarkText;

public WatermarkAdorner(UIElement adornedElement, string watermark)
    : base(adornedElement)
{
    _watermarkText = new TextBlock
    {
        Text = watermark,
        Foreground = Brushes.Gray,
        FontStyle = FontStyles.Italic,
        Margin = new Thickness(4, 2, 0, 0),
        IsHitTestVisible = false
    };

    AddVisualChild(_watermarkText);

    IsHitTestVisible = false;
}

protected override int VisualChildrenCount => 1;

protected override Visual GetVisualChild(int index) => _watermarkText;

protected override Size MeasureOverride(Size constraint)
{
    _watermarkText.Measure(constraint);
    return _watermarkText.DesiredSize;
}

protected override Size ArrangeOverride(Size finalSize)
{
    _watermarkText.Arrange(new Rect(finalSize));
    return finalSize;
}

}

4.2 Watermark Attached Property

namespace MyApp.Behaviors;

using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using MyApp.Adorners;

public static class Watermark { public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached( "Text", typeof(string), typeof(Watermark), new PropertyMetadata(null, OnTextChanged));

public static string GetText(DependencyObject obj) =>
    (string)obj.GetValue(TextProperty);

public static void SetText(DependencyObject obj, string value) =>
    obj.SetValue(TextProperty, value);

private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (d is not TextBox textBox)
    {
        return;
    }

    textBox.Loaded -= OnTextBoxLoaded;
    textBox.Loaded += OnTextBoxLoaded;
    textBox.TextChanged -= OnTextBoxTextChanged;
    textBox.TextChanged += OnTextBoxTextChanged;
}

private static void OnTextBoxLoaded(object sender, RoutedEventArgs e)
{
    if (sender is TextBox textBox)
    {
        UpdateWatermark(textBox);
    }
}

private static void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
    if (sender is TextBox textBox)
    {
        UpdateWatermark(textBox);
    }
}

private static void UpdateWatermark(TextBox textBox)
{
    var adornerLayer = AdornerLayer.GetAdornerLayer(textBox);
    if (adornerLayer is null)
    {
        return;
    }

    // Remove existing watermark
    RemoveWatermark(textBox, adornerLayer);

    // Add watermark if text is empty
    if (string.IsNullOrEmpty(textBox.Text))
    {
        var watermark = GetText(textBox);
        if (!string.IsNullOrEmpty(watermark))
        {
            adornerLayer.Add(new WatermarkAdorner(textBox, watermark));
        }
    }
}

private static void RemoveWatermark(TextBox textBox, AdornerLayer adornerLayer)
{
    var adorners = adornerLayer.GetAdorners(textBox);
    if (adorners is null)
    {
        return;
    }

    foreach (var adorner in adorners)
    {
        if (adorner is WatermarkAdorner)
        {
            adornerLayer.Remove(adorner);
        }
    }
}

}

4.3 Using Watermark in XAML

<TextBox local:Watermark.Text="Enter email address"/>

  1. Advanced Adorner Patterns

For advanced patterns, see references/advanced-adorners.md:

  • Resize Handle Adorner: Element resizing with corner/edge handles

  • Validation Error Adorner: Display validation errors with icons

  • Drag Preview Adorner: Visual feedback during drag operations

  • Adorner Management Service: Lifecycle management utilities

  1. Checklist
  • Verify AdornerLayer exists before adding Adorner

  • Set IsHitTestVisible = false for decoration-only Adorners

  • Correctly implement VisualChildrenCount and GetVisualChild

  • Arrange children using MeasureOverride and ArrangeOverride

  • Remove unnecessary Adorners (prevent memory leaks)

  • Explicitly add AdornerDecorator in Popup, etc.

  1. References
  • Adorners Overview - Microsoft Docs

  • Adorner Class - Microsoft Docs

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

converting-html-css-to-wpf-xaml

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

managing-styles-resourcedictionary

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

using-xaml-property-element-syntax

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

designing-avalonia-customcontrol-architecture

No summary provided by upstream source.

Repository SourceNeeds Review