Claude-skill-registry 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.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/implementing-wpf-adorners" ~/.claude/skills/majiayu000-claude-skill-registry-implementing-wpf-adorners && rm -rf "$T"
manifest:
skills/data/implementing-wpf-adorners/SKILL.mdsource content
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 |
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
for decoration-only AdornersIsHitTestVisible = false - Correctly implement VisualChildrenCount and GetVisualChild
- Arrange children using MeasureOverride and ArrangeOverride
- Remove unnecessary Adorners (prevent memory leaks)
- Explicitly add AdornerDecorator in Popup, etc.