一、定位
| 类型 |
定位 |
职责 |
| 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();
}
}
要点:
- **
ChildProperty**:注册了 AffectsMeasure & AffectsRender,更换子元素会自动重新布局和重绘。
- **
OnChildChanged**:手动把 Child 从视觉树中移除/添加,并 InvalidateMeasure。
- **
MeasureOverride/ArrangeOverride**:把布局权限全部委托给 Child。
- **
OnRender**:继承自UIElement,默认空,实现了“插入渲染点”。
- 视觉子元素:重写
VisualChildrenCount 与 GetVisualChild,使 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)
{
// 子类在这里画高亮框、拖拽句柄等
}
}
}
要点:
- 不作为 Child:
Adorner 不定义 Child 属性,也不参与常规视觉树;它直接 layer.Add(this)。
- 布局:Measure/Arrange 都硬编码成跟随
AdornedElement.RenderSize,完全独立于父级布局流。
- 渲染:
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:
- 测量
Child(ContentPresenter),
- 测量内部自动创建的
AdornerLayer,
- 返回二者的最大所需空间。
- 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)。
- 如果需要拦截或增强,可以在
OnMouseDown、OnPreviewMouseMove 等重写。
Adorner
- 也继承自
UIElement,默认是可命中(IsHitTestVisible = true)。
- 可以重写
HitTestCore,在装饰层里提供交互(如拖拽把柄)。
- 因为在 AdornerLayer 中,其事件路由直接到 Adorner,然后再冒泡到根。
六、对比
特性对比
| 特性 |
Decorator |
AdornerDecorator |
Adorner |
| 继承体系 |
FrameworkElement |
Decorator → FrameworkElement |
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 |
| 固定边框/背景 |
支持 (Border、OutlineDecorator) |
不适用 |
不适用 |
| 缩放/布局变换 |
支持 (Viewbox) |
不适用 |
不适用 |
| 选中高亮/拖拽把柄 |
不适用 |
支持 (提供 AdornerLayer 环境,后代可挂 Adorner) |
支持 (HighlightAdorner、DragAdorner 等) |
| 弹出浮层/遮罩 |
不适用 |
支持 (可作为 ControlTemplate 外层,包含 AdornerLayer) |
支持 (可在 AdornerLayer 上绘制简单遮罩) |