IHandle 接口详解 - 控制点接口

这是形状系统中控制点(Handle)的核心接口,定义了所有控制点必须实现的行为。控制点是用户与形状交互的"手柄",通过拖拽它们可以调整形状的大小、位置、旋转等属性。


📄 文件头部和命名空间

namespace H.LabelImg.ShapeBox.Shapes.Handles;
// 命名空间:存放形状的控制点相关类

🔌 IHandle 接口

// 接口定义:表示一个控制点(手柄)
// 控制点是形状周围的交互元素,用户可以通过拖拽它们来编辑形状
public interface IHandle
{
    // 属性:获取这个控制点所属的形状
    // 通过 Owner 可以知道拖拽时应该修改哪个形状
    IShape Owner { get; }
    
    // 属性:获取控制点的位置(坐标)
    // 注意:原代码拼写为 Postion(应该是 Position)
    Point Postion { get; }
    
    // 方法:检测指定的点是否击中了这个控制点
    // 参数:
    //   view - 视图对象(提供缩放、坐标转换等信息)
    //   position - 要检测的点(通常是鼠标点击位置)
    // 返回值:
    //   true - 点中了控制点
    //   false - 没有点中
    bool HitTestPoint(IView view, Point position);
    
    // 方法:绘制控制点
    // 参数:
    //   view - 视图对象
    //   drawingContext - WPF绘图上下文
    //   pen - 画笔(定义边框颜色、粗细等)
    //   fill - 填充颜色(可选,默认null)
    void Draw(IView view, DrawingContext drawingContext, Pen pen, Brush fill = null);
    
    // 属性:获取鼠标悬停在控制点上时应该显示的光标样式
    // 例如:
    //   - 角点控制点 → 对角线双箭头(↖↘ 或 ↗↙)
    //   - 边中点控制点 → 水平或垂直双箭头(←→ 或 ↑↓)
    //   - 旋转控制点 → 环形箭头(↺)
    Cursor Cursor { get; }
}

🎯 控制点的作用

控制点是形状与用户交互的桥梁

矩形形状及其控制点:

        ● 角点控制点                ● 角点控制点
        (改变大小)                  (改变大小)
        
    ●───────●───────●
    │       │       │
    │       │       │
    ●       ●       ●  ← 边中点控制点
    │       │       │   (单向拉伸)
    │       │       │
    ●───────●───────●
    
        ● 角点控制点                ● 角点控制点

📝 完整的控制点实现示例

示例1:矩形角点控制点

// 矩形角点控制点(用于改变矩形大小)
public class CornerHandle : IHandle
{
    private RectangleShape _owner;      // 所属的矩形
    private CornerType _corner;         // 哪个角(左上、右上、左下、右下)
    private double _size = 8;           // 控制点大小(像素)
    
    // 构造函数
    public CornerHandle(RectangleShape owner, CornerType corner)
    {
        _owner = owner;
        _corner = corner;
    }
    
    // 获取所属形状
    public IShape Owner => _owner;
    
    // 获取控制点位置
    public Point Postion
    {
        get
        {
            var bounds = _owner.Bounds;
            
            switch (_corner)
            {
                case CornerType.TopLeft:
                    return new Point(bounds.Left, bounds.Top);
                case CornerType.TopRight:
                    return new Point(bounds.Right, bounds.Top);
                case CornerType.BottomLeft:
                    return new Point(bounds.Left, bounds.Bottom);
                case CornerType.BottomRight:
                    return new Point(bounds.Right, bounds.Bottom);
                default:
                    return new Point(0, 0);
            }
        }
    }
    
    // 检测是否被点击
    public bool HitTestPoint(IView view, Point position)
    {
        Point handlePos = this.Postion;
        double half = _size / 2;
        
        // 检查点是否在控制点的矩形区域内
        return position.X >= handlePos.X - half &&
               position.X <= handlePos.X + half &&
               position.Y >= handlePos.Y - half &&
               position.Y <= handlePos.Y + half;
    }
    
    // 绘制控制点
    public void Draw(IView view, DrawingContext dc, Pen pen, Brush fill = null)
    {
        Point pos = this.Postion;
        double half = _size / 2;
        
        // 绘制一个小正方形(白色填充,边框使用传入的画笔)
        dc.DrawRectangle(fill ?? Brushes.White, pen, 
                         new Rect(pos.X - half, pos.Y - half, _size, _size));
    }
    
    // 鼠标悬停时显示对角线双箭头
    public Cursor Cursor
    {
        get
        {
            switch (_corner)
            {
                case CornerType.TopLeft:
                case CornerType.BottomRight:
                    return Cursors.SizeNWSE;  // ↖↘ 对角线箭头
                case CornerType.TopRight:
                case CornerType.BottomLeft:
                    return Cursors.SizeNESW;  // ↗↙ 对角线箭头
                default:
                    return Cursors.Hand;
            }
        }
    }
    
    // 更新形状(当拖拽时调用)
    public void UpdateShape(Vector delta)
    {
        var bounds = _owner.Bounds;
        
        switch (_corner)
        {
            case CornerType.TopLeft:
                bounds = new Rect(bounds.Left + delta.X, bounds.Top + delta.Y,
                                 bounds.Width - delta.X, bounds.Height - delta.Y);
                break;
            case CornerType.TopRight:
                bounds = new Rect(bounds.Left, bounds.Top + delta.Y,
                                 bounds.Width + delta.X, bounds.Height - delta.Y);
                break;
            case CornerType.BottomLeft:
                bounds = new Rect(bounds.Left + delta.X, bounds.Top,
                                 bounds.Width - delta.X, bounds.Height + delta.Y);
                break;
            case CornerType.BottomRight:
                bounds = new Rect(bounds.Left, bounds.Top,
                                 bounds.Width + delta.X, bounds.Height + delta.Y);
                break;
        }
        
        // 确保宽高为正数
        if (bounds.Width > 0 && bounds.Height > 0)
            _owner.Bounds = bounds;
    }
}

public enum CornerType
{
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight
}

示例2:矩形边中点控制点

// 边中点控制点(用于单向拉伸)
public class EdgeHandle : IHandle
{
    private RectangleShape _owner;
    private EdgeType _edge;      // 哪条边(上、下、左、右)
    private double _size = 8;
    
    public EdgeHandle(RectangleShape owner, EdgeType edge)
    {
        _owner = owner;
        _edge = edge;
    }
    
    public IShape Owner => _owner;
    
    public Point Postion
    {
        get
        {
            var bounds = _owner.Bounds;
            
            switch (_edge)
            {
                case EdgeType.Top:
                    return new Point(bounds.Left + bounds.Width / 2, bounds.Top);
                case EdgeType.Bottom:
                    return new Point(bounds.Left + bounds.Width / 2, bounds.Bottom);
                case EdgeType.Left:
                    return new Point(bounds.Left, bounds.Top + bounds.Height / 2);
                case EdgeType.Right:
                    return new Point(bounds.Right, bounds.Top + bounds.Height / 2);
                default:
                    return new Point(0, 0);
            }
        }
    }
    
    public bool HitTestPoint(IView view, Point position)
    {
        Point handlePos = this.Postion;
        double half = _size / 2;
        
        return position.X >= handlePos.X - half &&
               position.X <= handlePos.X + half &&
               position.Y >= handlePos.Y - half &&
               position.Y <= handlePos.Y + half;
    }
    
    public void Draw(IView view, DrawingContext dc, Pen pen, Brush fill = null)
    {
        Point pos = this.Postion;
        double half = _size / 2;
        
        // 绘制一个小圆形作为边中点控制点
        dc.DrawEllipse(fill ?? Brushes.White, pen, pos, half, half);
    }
    
    public Cursor Cursor
    {
        get
        {
            switch (_edge)
            {
                case EdgeType.Top:
                case EdgeType.Bottom:
                    return Cursors.SizeNS;    // ↕ 垂直双箭头
                case EdgeType.Left:
                case EdgeType.Right:
                    return Cursors.SizeWE;    // ↔ 水平双箭头
                default:
                    return Cursors.Hand;
            }
        }
    }
    
    public void UpdateShape(Vector delta)
    {
        var bounds = _owner.Bounds;
        
        switch (_edge)
        {
            case EdgeType.Top:
                bounds = new Rect(bounds.Left, bounds.Top + delta.Y,
                                 bounds.Width, bounds.Height - delta.Y);
                break;
            case EdgeType.Bottom:
                bounds = new Rect(bounds.Left, bounds.Top,
                                 bounds.Width, bounds.Height + delta.Y);
                break;
            case EdgeType.Left:
                bounds = new Rect(bounds.Left + delta.X, bounds.Top,
                                 bounds.Width - delta.X, bounds.Height);
                break;
            case EdgeType.Right:
                bounds = new Rect(bounds.Left, bounds.Top,
                                 bounds.Width + delta.X, bounds.Height);
                break;
        }
        
        if (bounds.Width > 0 && bounds.Height > 0)
            _owner.Bounds = bounds;
    }
}

public enum EdgeType
{
    Top,
    Bottom,
    Left,
    Right
}

示例3:圆形控制点

// 圆形形状的控制点
public class CircleHandle : IHandle
{
    private CircleShape _owner;
    private double _size = 8;
    
    public CircleHandle(CircleShape owner)
    {
        _owner = owner;
    }
    
    public IShape Owner => _owner;
    
    // 控制点位置:圆的右边缘
    public Point Postion => new Point(_owner.Center.X + _owner.Radius, _owner.Center.Y);
    
    public bool HitTestPoint(IView view, Point position)
    {
        Point handlePos = this.Postion;
        double half = _size / 2;
        
        return position.X >= handlePos.X - half &&
               position.X <= handlePos.X + half &&
               position.Y >= handlePos.Y - half &&
               position.Y <= handlePos.Y + half;
    }
    
    public void Draw(IView view, DrawingContext dc, Pen pen, Brush fill = null)
    {
        Point pos = this.Postion;
        double half = _size / 2;
        
        // 绘制一个小圆点作为控制点
        dc.DrawEllipse(fill ?? Brushes.White, pen, pos, half, half);
    }
    
    // 改变大小时显示对角线箭头
    public Cursor => Cursors.SizeNWSE;
    
    // 更新圆形大小
    public void UpdateShape(Vector delta)
    {
        double newRadius = _owner.Radius + delta.X;
        if (newRadius > 5)  // 最小半径5像素
            _owner.Radius = newRadius;
    }
}

示例4:旋转控制点

// 旋转控制点(通常放在形状上方)
public class RotateHandle : IHandle
{
    private IShape _owner;
    private Func<Point> _getCenter;      // 获取形状中心点的函数
    private double _distance = 50;       // 控制点离中心的距离
    private double _size = 10;
    
    public RotateHandle(IShape owner, Func<Point> getCenter)
    {
        _owner = owner;
        _getCenter = getCenter;
    }
    
    public IShape Owner => _owner;
    
    // 控制点位置:形状中心的正上方
    public Point Postion
    {
        get
        {
            Point center = _getCenter();
            return new Point(center.X, center.Y - _distance);
        }
    }
    
    public bool HitTestPoint(IView view, Point position)
    {
        Point handlePos = this.Postion;
        double half = _size / 2;
        
        return position.X >= handlePos.X - half &&
               position.X <= handlePos.X + half &&
               position.Y >= handlePos.Y - half &&
               position.Y <= handlePos.Y + half;
    }
    
    public void Draw(IView view, DrawingContext dc, Pen pen, Brush fill = null)
    {
        Point pos = this.Postion;
        double half = _size / 2;
        
        // 绘制一个圆形旋转控制点
        dc.DrawEllipse(fill ?? Brushes.White, pen, pos, half, half);
        
        // 绘制连接线(从中心到控制点)
        Point center = _getCenter();
        dc.DrawLine(pen, center, pos);
    }
    
    // 旋转时显示环形箭头
    public Cursor => Cursors.Hand;
    
    // 这里需要额外的旋转角度属性
    public double RotationAngle { get; set; }
}

🖱️ 光标样式说明

// WPF 内置光标样式
Cursors.Arrow      // 普通箭头 →
Cursors.Hand       // 手形 🖐
Cursors.SizeNS     // 垂直双箭头 ↕
Cursors.SizeWE     // 水平双箭头 ↔
Cursors.SizeNWSE   // 对角线箭头 ↖↘
Cursors.SizeNESW   // 对角线箭头 ↗↙
Cursors.ScrollAll  // 四向箭头 ✛

不同控制点的光标

        ↺ 旋转控制点(环形箭头)
        ↑
    ↖───●───↗
    │       │
    ←   ●   → 边中点(水平/垂直箭头)
    │       │
    ↙───●───↘
        ↓
角点控制点(对角线箭头)

🔄 在 HandleShapeBase 中的使用

public class RectangleShape : HandleShapeBase
{
    public Rect Bounds { get; set; }
    
    protected override IEnumerable<IHandle> CreateHandles()
    {
        // 创建8个控制点
        yield return new CornerHandle(this, CornerType.TopLeft);
        yield return new EdgeHandle(this, EdgeType.Top);
        yield return new CornerHandle(this, CornerType.TopRight);
        yield return new EdgeHandle(this, EdgeType.Left);
        yield return new EdgeHandle(this, EdgeType.Right);
        yield return new CornerHandle(this, CornerType.BottomLeft);
        yield return new EdgeHandle(this, EdgeType.Bottom);
        yield return new CornerHandle(this, CornerType.BottomRight);
    }
    
    // 在选中时显示控制点
    public override void DrawSelect(IView view, DrawingContext dc, 
                                     Brush stroke, double thickness, Brush fill)
    {
        base.DrawSelect(view, dc, stroke, thickness, fill);
        this.UseHandle = true;  // 显示控制点
    }
}

设计模式分析

1. 策略模式

不同的控制点实现不同的交互策略(改变大小、旋转、移动等)。

2. 观察者模式

控制点通过 Owner 属性关联形状,拖拽时更新形状。

3. 工厂模式

形状通过 CreateHandles() 方法创建自己的控制点集合。


总结

成员 类型 职责
Owner 属性 获取所属形状
Postion 属性 获取控制点位置
HitTestPoint() 方法 检测是否被点击
Draw() 方法 绘制控制点
Cursor 属性 获取鼠标悬停时的光标样式

核心设计思想

  1. 统一接口:所有控制点都实现相同的接口
  2. 自包含:每个控制点知道自己的位置、绘制方式、交互行为
  3. 可扩展:添加新类型的控制点只需实现 IHandle 接口

这个接口是形状编辑系统的关键抽象,让形状可以拥有各种可拖拽的控制点,实现丰富的交互功能!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐