以下是 WPF 中 装饰器(Adorner) 的常用方法、属性、关键概念和开发要点的完整整理,特别适合用于实现选中框、拖拽手柄、尺寸调整器、高亮边框等交互功能。
🧱 一、Adorner 基础结构
Adorner
继承自 FrameworkElement
→ UIElement
,因此拥有大部分 UI 元素的能力,但不参与布局(Layout),只参与渲染(Rendering)和命中测试(Hit Testing)。
🔑 二、核心属性(最常用)
属性 | 类型 | 说明 |
---|---|---|
AdornedElement | UIElement | ✅ 最重要的属性! 被装饰的目标元素。所有位置、尺寸通常基于它。 |
DesiredSize | Size | 装饰器期望的大小(但装饰器不参与布局,此值通常无实际影响)。 |
RenderSize | Size | 实际渲染大小(通常等于 AdornedElement.RenderSize ,但可自定义)。 |
IsHitTestVisible | bool | 是否响应鼠标/触摸事件(默认 true )。若只想显示不交互,设为 false 。 |
Opacity , Visibility | - | 支持透明度和可见性控制。 |
💡 提示:装饰器的坐标系原点(0,0)
对应AdornedElement
的左上角(在装饰层中的位置已自动对齐)。
🛠️ 三、常用方法
1. 重绘相关
方法 | 说明 |
---|---|
InvalidateVisual() | ✅ 强制重绘:标记为无效,触发 OnRender 。 |
OnRender(DrawingContext dc) | 必须重写:在此绘制图形(边框、手柄、图标等)。 |
2. 命中测试 & 交互
方法 | 说明 |
---|---|
HitTestCore(PointHitTestParameters) | 自定义点击检测逻辑(可选)。 |
OnMouseDown , OnMouseMove , OnMouseUp | 处理鼠标事件(需 IsHitTestVisible = true )。 |
3. 生命周期 & 管理
方法 | 说明 |
---|---|
OnVisualParentChanged(DependencyObject oldParent) | 装饰器被添加/移除时调用(可用于清理事件订阅)。 |
OnRenderSizeChanged(SizeChangedInfo sizeInfo) | 渲染尺寸变化时调用(较少用)。 |
🔄 四、关键事件(常用于监听被装饰元素)
虽然 Adorner
本身不自动响应 AdornedElement
的变化,但你可以手动订阅:
public MyAdorner(UIElement adornedElement) : base(adornedElement)
{
// 监听尺寸变化 → 触发重绘
if (adornedElement is FrameworkElement fe)
{
fe.SizeChanged += OnAdornedElementSizeChanged;
fe.LayoutUpdated += OnAdornedElementLayoutUpdated; // 谨慎使用(高频)
}
}
private void OnAdornedElementSizeChanged(object sender, SizeChangedEventArgs e)
{
InvalidateVisual();
}
⚠️ 注意:记得在 OnVisualParentChanged
中取消订阅,避免内存泄漏!
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (AdornedElement is FrameworkElement fe)
{
fe.SizeChanged -= OnAdornedElementSizeChanged;
}
base.OnVisualParentChanged(oldParent);
}
📐 五、坐标与定位技巧
装饰器的位置由 WPF 自动对齐到 AdornedElement
,但你可以:
- 在
OnRender
中使用AdornedElement.RenderSize
获取当前尺寸; - 绘制时使用
Rect(0, 0, width, height)
即可覆盖被装饰元素; - 若需绘制在元素外部(如外边框),可使用负坐标或更大矩形。
protected override void OnRender(DrawingContext drawingContext)
{
var size = AdornedElement.RenderSize;
var rect = new Rect(-2, -2, size.Width + 4, size.Height + 4); // 外扩 2px
drawingContext.DrawRectangle(null, new Pen(Brushes.Red, 2), rect);
}
🧩 六、完整示例:基础选中边框装饰器
public class SelectionAdorner : Adorner
{
private Pen _pen = new Pen(Brushes.DodgerBlue, 2) { DashStyle = DashStyles.Dash };
public SelectionAdorner(UIElement adornedElement) : base(adornedElement)
{
IsHitTestVisible = false; // 不拦截鼠标事件
if (adornedElement is FrameworkElement fe)
{
fe.SizeChanged += OnSizeChanged;
}
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
var size = AdornedElement.RenderSize;
if (size.Width <= 0 || size.Height <= 0) return;
var rect = new Rect(0, 0, size.Width, size.Height);
drawingContext.DrawRectangle(null, _pen, rect);
}
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (AdornedElement is FrameworkElement fe)
{
fe.SizeChanged -= OnSizeChanged;
}
base.OnVisualParentChanged(oldParent);
}
}
🧹 七、最佳实践 & 注意事项
项目 | 建议 |
---|---|
内存泄漏 | 订阅 AdornedElement 事件后,务必在 OnVisualParentChanged 中取消订阅。 |
性能 | 避免在 OnRender 中做复杂计算;缓存 Pen 、Brush 等资源。 |
Z-Order | 所有装饰器绘制在同一层,后添加的在上层。 |
获取装饰层 | 使用 AdornerLayer.GetAdornerLayer(element) 添加/移除。 |
移除装饰器 | 调用 adornerLayer.Remove(adorner) ,不要只设为 null 。 |
调试技巧 | 给装饰器设置 Background = new SolidColorBrush(Color.FromArgb(20, 255, 0, 0)) 可视化区域。 |
📚 八、常用工具方法(辅助)
// 获取或添加装饰器(专用于你的类型)
public static SelectionAdorner GetOrAddSelectionAdorner(UIElement element)
{
var layer = AdornerLayer.GetAdornerLayer(element);
if (layer == null) return null;
var adorners = layer.GetAdorners(element);
if (adorners?.FirstOrDefault(a => a is SelectionAdorner) is SelectionAdorner existing)
return existing;
var newAdorner = new SelectionAdorner(element);
layer.Add(newAdorner);
return newAdorner;
}
// 移除所有指定类型的装饰器
public static void RemoveSelectionAdorners(UIElement element)
{
var layer = AdornerLayer.GetAdornerLayer(element);
if (layer == null) return;
var adorners = layer.GetAdorners(element);
if (adorners == null) return;
foreach (var adorner in adorners.OfType<SelectionAdorner>().ToArray())
{
layer.Remove(adorner);
}
}
✅ 总结:Adorner 开发 Checklist
- [ ] 重写
OnRender
绘制内容 - [ ] 使用
AdornedElement.RenderSize
获取尺寸 - [ ] 监听
SizeChanged
并调用InvalidateVisual()
- [ ] 设置
IsHitTestVisible
控制是否响应鼠标 - [ ] 在
OnVisualParentChanged
中清理事件订阅 - [ ] 通过
AdornerLayer
添加/移除装饰器 - [ ] 避免内存泄漏(不要持有外部强引用)
有了这份整理,你就可以高效、安全地开发各种装饰器了!如需特定场景(如缩放手柄、旋转控制点等),也可以继续提问。
Qwen生成的
本文由 jxxxy 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。