LOADING

加载过慢请开启缓存 浏览器默认开启

WPF中的Decorator、Adorner和AdornerDecorator

一、定位

类型 定位 职责
Decorator 面向布局/渲染管线的“包装器” 持有单一子元素,参与 Measure/Arrange,允许在 OnRender 中“插入”绘制
Adorner 面向视觉叠加的“覆盖器”,不参与父布局 挂载在 AdornerLayer 上,不影响被装饰元素布局,仅负责 OnRender 叠加绘制
AdornerDecorator 一个 Decorator,自动为其子元素创建并管理 AdornerLayer 既是布局/渲染包装器,又在内部插入 AdornerLayer,为后代元素提供 Adorner 服务

二、继承体系

System.Object
 └─ DispatcherObject         // Dispatcher 线程亲缘
     └─ DependencyObject     // 依赖属性系统
         └─ Visual           // 低级渲染节点
             └─ UIElement     // 布局调度 + 输入 + 路由事件
                 └─ FrameworkElement  // 逻辑树 + 资源 + DataContext + Loaded/Unloaded
                     ├─ Decorator        ←───【1】
                     │    └─ AdornerDecorator ←───【2】
                     ├─ Panel
                     ├─ Control
                     ├─ ContentPresenter
                     └─ … 
                     
而 Adorner 虽然也是 FrameworkElement 的子类,但它不在上面那条“常规模板”分支里,而是单独挂在 AdornerLayer:

FrameworkElement
 └─ Adorner               ←───【3】
  • 【1】Decorator:定义在 PresentationFramework 程序集,负责“包裹一个 Child 并参与布局/渲染”。
  • 【2】AdornerDecorator:继承自 Decorator,在其内部模板里插入一个 AdornerLayer,自动为后代提供 Adorner 服务。
  • 【3】Adorner:也是 FrameworkElement,但是不作为常规子元素出现在逻辑/视觉树,而是由 AdornerLayer 管理,专职做“叠加”渲染。

源码位于 dotnet/wpf 源码仓库 ,路径均基于主分支 main 下的 src/Microsoft.DotNet.Wpf/src/PresentationFramework

路径
Decorator PresentationFramework/System/Windows/Controls/Decorator.cs
AdornerDecorator PresentationFramework/System/Windows/Documents/AdornerDecorator.cs
Adorner PresentationFramework/System/Windows/Documents/Adorner.cs

2.1 Decorator(Controls/Decorator.cs)

namespace System.Windows.Controls
{
    [ContentProperty("Child")]
    public abstract class Decorator : FrameworkElement
    {
        // —— Child 属性 —— //
        public static readonly DependencyProperty ChildProperty =
            DependencyProperty.Register(
                "Child", typeof(UIElement), typeof(Decorator),
                new FrameworkPropertyMetadata(
                    null,
                    FrameworkPropertyMetadataOptions.AffectsMeasure
                  | FrameworkPropertyMetadataOptions.AffectsRender,
                    OnChildChanged));

        public UIElement Child
        {
            get => (UIElement)GetValue(ChildProperty);
            set => SetValue(ChildProperty, value);
        }

        private static void OnChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var deco = (Decorator)d;
            if (e.OldValue is UIElement oldChild)
                deco.RemoveVisualChild(oldChild);
            if (e.NewValue is UIElement newChild)
                deco.AddVisualChild(newChild);

            deco.InvalidateMeasure();
        }

        // —— 布局管线 —— //
        protected override Size MeasureOverride(Size availableSize)
        {
            if (Child != null)
            {
                Child.Measure(availableSize);
                return Child.DesiredSize;
            }
            return new Size();
        }

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

        // —— 渲染管线 —— //
        protected override void OnRender(DrawingContext drawingContext)
        {
            // 默认什么都不画,留给子类
        }

        // —— 视觉子元素管理 —— //
        protected override int VisualChildrenCount => Child != null ? 1 : 0;
        protected override Visual GetVisualChild(int index)
            => (index == 0 && Child != null) ? Child : throw new ArgumentOutOfRangeException();
    }
}

要点

  1. **ChildProperty**:注册了 AffectsMeasure & AffectsRender,更换子元素会自动重新布局和重绘。
  2. **OnChildChanged**:手动把 Child 从视觉树中移除/添加,并 InvalidateMeasure。
  3. **MeasureOverride/ArrangeOverride**:把布局权限全部委托给 Child。
  4. **OnRender**:继承自UIElement,默认空,实现了“插入渲染点”。
  5. 视觉子元素:重写 VisualChildrenCountGetVisualChild,使 Decorator 正常成为视觉树节点。

2.2 AdornerDecorator(Documents/AdornerDecorator.cs)

namespace System.Windows.Controls
{
    public sealed class AdornerDecorator : Decorator
    {
        private AdornerLayer _adornerLayer;

        public AdornerDecorator()
        {
            // nothing special in constructor
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            // 1. 创建一个 Grid:行列都只有 1 个
            // 2. Grid.Children.Add(ChildPresenter)
            // 3. _adornerLayer = new AdornerLayer(); Grid.Children.Add(_adornerLayer);
            // 4. this.Child = grid;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if (Child != null)
            {
                Child.Measure(availableSize);
                return Child.DesiredSize;
            }
            return new Size();
        }

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

要点

  • 继承自 Decorator:所以拥有同样的 Child 布局与渲染点。
  • **OnApplyTemplate**:在模板应用后,把原本的 Child 包进一个 Grid,再向 Grid 插入一个 AdornerLayer,并把整个 Grid 设为新的 Child
  • 效果:在 AdornerLayer.GetAdornerLayer(target) 时,就会获得内部那一层 AdornerLayer 对象,无需手动在 XAML 写 <AdornerLayer/>

2.3 Adorner(Documents/Adorner.cs)

namespace System.Windows.Documents
{
    public abstract class Adorner : FrameworkElement
    {
        // 被装饰对象
        public UIElement AdornedElement { get; }

        public Adorner(UIElement adornedElement) : base()
        {
            if (adornedElement == null) throw new ArgumentNullException(nameof(adornedElement));
            AdornedElement = adornedElement;
            // 把自己添加到对应 AdornerLayer
            AdornerLayer layer = AdornerLayer.GetAdornerLayer(adornedElement);
            layer?.Add(this);
        }

        // —— 固定 Measure/Arrange —— //
        protected override Size MeasureOverride(Size constraint)
            => AdornedElement.RenderSize;
        protected override Size ArrangeOverride(Size finalSize)
            => finalSize;

        // —— 视觉子元素 = 0 —— //
        protected override int VisualChildrenCount => 0;
        protected override Visual GetVisualChild(int index) 
            => throw new ArgumentOutOfRangeException();

        // —— 叠加渲染 —— //
        protected override void OnRender(DrawingContext drawingContext)
        {
            // 子类在这里画高亮框、拖拽句柄等
        }
    }
}

要点

  1. 不作为 ChildAdorner 不定义 Child 属性,也不参与常规视觉树;它直接 layer.Add(this)
  2. 布局:Measure/Arrange 都硬编码成跟随 AdornedElement.RenderSize,完全独立于父级布局流。
  3. 渲染OnRender 成为唯一的“绘制入口”,方便做交互式覆盖。

三、布局管线(Measure / Arrange)

Decorator

protected override Size MeasureOverride(Size availableSize)
{
    if (Child != null)
    {
        Child.Measure(availableSize);
        return Child.DesiredSize;
    }
    return new Size();
}

protected override Size ArrangeOverride(Size finalSize)
{
    Child?.Arrange(new Rect(finalSize));
    return finalSize;
}
  • 参与:完全参与父容器的 Measure/Arrange。
  • 可扩展:子类可在 MeasureOverride/ArrangeOverride 前后插入逻辑(如 Viewbox 在 ArrangeOverride 中应用缩放)。

Adorner

protected override Size MeasureOverride(Size constraint)
    => AdornedElement.RenderSize;
protected override Size ArrangeOverride(Size finalSize)
    => finalSize;
  • 不参与:不走父容器的 Measure/Arrange 流程,而是固定使用 AdornedElement.RenderSize
  • 由 AdornerLayer 管理AdornerLayer 在其 Measure/Arrange 阶段,会遍历所有挂在它上面的 Adorner,并调用它们的 Measure/Arrange。

AdornerDecorator

  • MeasureOverride
    1. 测量 Child(ContentPresenter),
    2. 测量内部自动创建的 AdornerLayer
    3. 返回二者的最大所需空间。
  • ArrangeOverride:同理,会给 ContentPresenter 和 AdornerLayer 一样的 finalSize。

四、渲染(OnRender)

Decorator Adorner AdornerDecorator
OnRender 默认空,实现“前后插入”点 默认空,子类在这里绘制覆盖层 默认空,不常被直接重写;其子 AdornerLayer 会绘制所有 Adorner
子类示例 Border:先 draw background → base → draw border HighlightAdorner:绘制高亮矩形 AdornerDecorator 不绘制,负责承载 ContentPresenter 和 AdornerLayer
  • Decorator 的子类(如 Border)重写 OnRender,在 base 之后或之前调用 DrawingContext 完成交叉绘制。
  • Adorner 的子类在 OnRender 中做覆盖式绘制,不担心布局影响。
  • AdornerDecorator 并不直接绘制,它的“装饰”机制是容纳 AdornerLayer

五、事件路由与命中测试

Decorator & AdornerDecorator

  • 都是 UIElement,可接收常规鼠标/键盘路由事件(Tunnel/Bubble)。
  • 如果需要拦截或增强,可以在 OnMouseDownOnPreviewMouseMove 等重写。

Adorner

  • 也继承自 UIElement,默认是可命中IsHitTestVisible = true)。
  • 可以重写 HitTestCore,在装饰层里提供交互(如拖拽把柄)。
  • 因为在 AdornerLayer 中,其事件路由直接到 Adorner,然后再冒泡到根。

六、对比

特性对比

特性 Decorator AdornerDecorator Adorner
继承体系 FrameworkElement DecoratorFrameworkElement FrameworkElement
子元素管理 public UIElement Child {get;set;} 继承自 Decorator,Child 替为包含 Grid + AdornerLayer Child,自身不管理子元素
视觉树位置 普通视觉树节点 同 Decorator,但内部含一层 AdornerLayer 不在普通视觉树,由对应的 AdornerLayer 管理
布局参与 Measure/Arrange 全部委托给 Child 同 Decorator Measure 固定为 AdornedElement.RenderSize Arrange 返回同尺寸
渲染入口 OnRender 空实现,子类可覆盖 同 Decorator OnRender 空实现,子类可覆盖
模板依赖 需在模板或 OnApplyTemplate 中注入 AdornerLayer

应用场景对比

场景 Decorator AdornerDecorator Adorner
固定边框/背景 支持 (BorderOutlineDecorator) 不适用 不适用
缩放/布局变换 支持 (Viewbox) 不适用 不适用
选中高亮/拖拽把柄 不适用 支持 (提供 AdornerLayer 环境,后代可挂 Adorner) 支持 (HighlightAdornerDragAdorner 等)
弹出浮层/遮罩 不适用 支持 (可作为 ControlTemplate 外层,包含 AdornerLayer 支持 (可在 AdornerLayer 上绘制简单遮罩)