Skip to content
implementing-wpf-adorners logo

WPF Adorner Patterns

implementing-wpf-adorners

Implements WPF Adorner decoration layers with AdornerLayer, AdornerDecorator, and custom Adorner patterns. Use when building drag handles, validation indicators, watermarks, selection visuals, or resize grips.

SKILL.md

Full skill instructions

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

ScenarioDescription
Validation DisplayInput field error display
Drag HandlesElement move/​resize handles
WatermarkHint text for empty TextBox
Selection DisplayHighlight selected elements
Tooltip/​BadgeAdditional info display on elements
Drag and DropPreview during drag

2. 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);
    }
}

3. 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>

4. 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"/>

5. 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

6. 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.

7. References

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View