WPF装饰器常用方法
in C# with 0 comment

以下是 WPF 中 装饰器(Adorner) 的常用方法、属性、关键概念和开发要点的完整整理,特别适合用于实现选中框、拖拽手柄、尺寸调整器、高亮边框等交互功能。


🧱 一、Adorner 基础结构

Adorner 继承自 FrameworkElementUIElement,因此拥有大部分 UI 元素的能力,但不参与布局(Layout),只参与渲染(Rendering)和命中测试(Hit Testing)


🔑 二、核心属性(最常用)

属性类型说明
AdornedElementUIElement最重要的属性! 被装饰的目标元素。所有位置、尺寸通常基于它。
DesiredSizeSize装饰器期望的大小(但装饰器不参与布局,此值通常无实际影响)。
RenderSizeSize实际渲染大小(通常等于 AdornedElement.RenderSize,但可自定义)。
IsHitTestVisiblebool是否响应鼠标/触摸事件(默认 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,但你可以:

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 中做复杂计算;缓存 PenBrush 等资源。
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


有了这份整理,你就可以高效、安全地开发各种装饰器了!如需特定场景(如缩放手柄、旋转控制点等),也可以继续提问。

Qwen生成的