HandleBase
·
HandleBase 详解 - 控制点基类
这是形状系统中控制点的抽象基类,为所有具体的控制点(角点、边中点、旋转点等)提供了通用的位置管理、绘制和命中检测功能。
📄 文件头部和引用
// 版权信息(同前)
// Copyright (c) HeBianGu Authors. All Rights Reserved.
// ...(省略)
using System.Windows.Input;
// 导入光标相关类型(Cursor)
namespace H.LabelImg.ShapeBox.Shapes.Handles;
// 命名空间:存放形状的控制点相关类
🏗️ HandleBase 抽象类
// 抽象基类:为所有控制点提供通用实现
// 实现 IHandle 接口
public abstract class HandleBase : IHandle
{
// 构造函数:创建控制点时调用
// 参数:
// postion - 控制点的位置(坐标)
// shape - 控制点所属的形状
public HandleBase(Point postion, IShape shape)
{
this.Postion = postion; // 设置位置
this.Owner = shape; // 设置所属形状
}
// 所属形状(只读,只能在构造函数中设置)
public IShape Owner { get; }
// 控制点位置(可读写)
// 注意:原代码拼写为 Postion(应该是 Position)
public Point Postion { get; set; }
// 控制点的大小(边长/直径,默认6像素)
public double Length { get; set; } = 6.0;
// 鼠标悬停时的光标样式(可被子类重写)
public virtual Cursor Cursor { get; set; }
// 绘制控制点(虚方法,子类可重写)
public virtual void Draw(IView view, DrawingContext drawingContext,
Pen pen, Brush fill = null)
{
// 调用扩展方法绘制控制点
// DrawHandle 会绘制一个小矩形(默认白色填充,边框使用传入的画笔)
drawingContext.DrawHandle(
this.Postion, // 控制点位置
pen.Brush, // 边框颜色
pen.Thickness, // 边框粗细
this.Length / view.Scale // 控制点大小(考虑缩放)
);
}
// 命中检测:判断点是否点击了控制点
public virtual bool HitTestPoint(IView view, Point position)
{
// 计算控制点的半长(考虑缩放)
// view.Scale 是视图缩放比例
// 缩放越大,控制点的命中区域越小(保持视觉一致性)
var d = 0.5 * this.Length / view.Scale;
// 计算控制点位置与点击位置的偏移
var v = this.Postion - position;
// 检查点击位置是否在控制点的矩形范围内
// 条件:X偏移和Y偏移的绝对值都小于半长
return Math.Abs(v.X) < d && Math.Abs(v.Y) < d;
}
}
🎯 核心功能详解
1. 构造函数 - 建立控制点与形状的关联
public HandleBase(Point postion, IShape shape)
{
this.Postion = postion;
this.Owner = shape;
}
作用:每个控制点都必须知道:
- 它在哪里(位置)
- 它属于谁(所属形状)
形状(矩形) 控制点(角点)
┌─────────────────┐
│ │ Owner ──→ 矩形
│ │ Position ──→ (100, 100)
│ │
└─────────────────┘
● ← 控制点位置 (100, 100)
2. Length 属性 - 控制点大小
public double Length { get; set; } = 6.0;
视觉效果:
Length = 4: Length = 6: Length = 8:
┌──┐ ┌────┐ ┌──────┐
│ │ │ │ │ │
└──┘ └────┘ └──────┘
(小) (中) (大)
3. Draw 方法 - 绘制控制点
public virtual void Draw(IView view, DrawingContext drawingContext,
Pen pen, Brush fill = null)
{
drawingContext.DrawHandle(
this.Postion,
pen.Brush,
pen.Thickness,
this.Length / view.Scale // ← 关键:考虑缩放
);
}
缩放处理的重要性:
视图缩放 = 1.0(原始大小) 视图缩放 = 2.0(放大2倍)
Length = 6 Length / Scale = 6 / 2 = 3
显示大小:6像素 显示大小:3像素
┌────┐ ┌──┐
│ │ │ │
└────┘ └──┘
视觉效果:大小适中 视觉效果:大小适中
(不缩放会显示为12像素,太大!)
4. HitTestPoint 方法 - 命中检测
public virtual bool HitTestPoint(IView view, Point position)
{
var d = 0.5 * this.Length / view.Scale;
var v = this.Postion - position;
return Math.Abs(v.X) < d && Math.Abs(v.Y) < d;
}
几何原理:
控制点区域(正方形):
d = 半长
←───→
┌─────────┐
│ ● │ ← 控制点中心 (Postion)
│ │
└─────────┘
命中条件:
|Postion.X - position.X| < d AND
|Postion.Y - position.Y| < d
即:点击位置在控制点正方形内
示例计算:
// 假设缩放为 1.0,Length = 6
d = 0.5 * 6 / 1.0 = 3
控制点中心 (100, 100)
命中区域:X 在 97~103,Y 在 97~103
点击 (100, 100) → 命中 ✅
点击 (95, 100) → X差=5,大于3 → 未命中 ❌
点击 (100, 95) → Y差=5,大于3 → 未命中 ❌
📝 具体控制点实现示例
示例1:角点控制点(继承 HandleBase)
// 矩形角点控制点
public class CornerHandle : HandleBase
{
private RectangleShape _rectangle;
private CornerType _cornerType;
public CornerHandle(RectangleShape rectangle, CornerType cornerType, Point position)
: base(position, rectangle)
{
_rectangle = rectangle;
_cornerType = cornerType;
// 设置光标样式
switch (cornerType)
{
case CornerType.TopLeft:
case CornerType.BottomRight:
this.Cursor = Cursors.SizeNWSE; // ↖↘
break;
case CornerType.TopRight:
case CornerType.BottomLeft:
this.Cursor = Cursors.SizeNESW; // ↗↙
break;
}
}
// 更新形状的方法(在拖拽时调用)
public void UpdateShape(Vector delta)
{
var bounds = _rectangle.Bounds;
switch (_cornerType)
{
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;
// ... 其他角点类似
}
if (bounds.Width > 0 && bounds.Height > 0)
{
_rectangle.Bounds = bounds;
// 更新控制点位置
this.Postion = GetPosition();
}
}
private Point GetPosition()
{
var bounds = _rectangle.Bounds;
switch (_cornerType)
{
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);
}
}
}
示例2:边中点控制点
public class EdgeHandle : HandleBase
{
private RectangleShape _rectangle;
private EdgeType _edgeType;
public EdgeHandle(RectangleShape rectangle, EdgeType edgeType, Point position)
: base(position, rectangle)
{
_rectangle = rectangle;
_edgeType = edgeType;
// 设置光标样式
switch (edgeType)
{
case EdgeType.Top:
case EdgeType.Bottom:
this.Cursor = Cursors.SizeNS; // ↕ 垂直
break;
case EdgeType.Left:
case EdgeType.Right:
this.Cursor = Cursors.SizeWE; // ↔ 水平
break;
}
}
public void UpdateShape(Vector delta)
{
var bounds = _rectangle.Bounds;
switch (_edgeType)
{
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)
{
_rectangle.Bounds = bounds;
this.Postion = GetPosition();
}
}
private Point GetPosition()
{
var bounds = _rectangle.Bounds;
switch (_edgeType)
{
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);
}
}
}
示例3:圆形控制点
public class CircleHandle : HandleBase
{
private CircleShape _circle;
public CircleHandle(CircleShape circle, Point position)
: base(position, circle)
{
_circle = circle;
this.Cursor = Cursors.SizeNWSE;
}
public void UpdateShape(Vector delta)
{
// 根据拖拽改变半径
double newRadius = _circle.Radius + delta.X;
if (newRadius > 5)
{
_circle.Radius = newRadius;
// 更新控制点位置(保持在圆的最右边)
this.Postion = new Point(_circle.Center.X + _circle.Radius, _circle.Center.Y);
}
}
}
🎨 自定义控制点示例
带特殊绘制的控制点
public class SpecialHandle : HandleBase
{
public SpecialHandle(Point position, IShape shape) : base(position, shape)
{
this.Length = 10; // 更大一点
this.Cursor = Cursors.Hand;
}
// 重写绘制方法
public override void Draw(IView view, DrawingContext dc, Pen pen, Brush fill = null)
{
// 绘制一个圆形而不是矩形
double radius = this.Length / view.Scale / 2;
dc.DrawEllipse(fill ?? Brushes.Yellow, pen, this.Postion, radius, radius);
}
// 重写命中检测(圆形区域)
public override bool HitTestPoint(IView view, Point position)
{
double radius = this.Length / view.Scale / 2;
double dx = this.Postion.X - position.X;
double dy = this.Postion.Y - position.Y;
double distance = Math.Sqrt(dx * dx + dy * dy);
return distance <= radius;
}
}
🔄 在形状中的使用
public class RectangleShape : HandleShapeBase
{
public Rect Bounds { get; set; }
protected override IEnumerable<IHandle> CreateHandles()
{
// 创建8个控制点
yield return new CornerHandle(this, CornerType.TopLeft,
new Point(Bounds.Left, Bounds.Top));
yield return new EdgeHandle(this, EdgeType.Top,
new Point(Bounds.Left + Bounds.Width / 2, Bounds.Top));
yield return new CornerHandle(this, CornerType.TopRight,
new Point(Bounds.Right, Bounds.Top));
// ... 其他控制点
}
// 当形状改变时更新控制点位置
public void UpdateHandles()
{
foreach (var handle in CreateHandles())
{
if (handle is CornerHandle corner)
corner.Postion = GetCornerPosition(corner.CornerType);
// ... 更新其他控制点
}
}
}
设计模式分析
1. 模板方法模式
基类定义了控制点的通用行为(绘制、命中检测),子类可以重写特定行为。
2. 依赖注入
通过构造函数注入所属形状,实现控制点与形状的关联。
3. 策略模式
不同的控制点实现不同的拖拽策略(角点改变大小、边中点单向拉伸等)。
总结
| 成员 | 类型 | 用途 |
|---|---|---|
Owner |
属性 | 控制点所属的形状 |
Postion |
属性 | 控制点位置(支持动态更新) |
Length |
属性 | 控制点大小(默认6像素) |
Cursor |
属性 | 鼠标光标样式 |
Draw() |
方法 | 绘制控制点(考虑缩放) |
HitTestPoint() |
方法 | 检测点击(考虑缩放) |
核心设计亮点:
- 缩放自适应:控制点大小和命中区域随视图缩放自动调整
- 位置可更新:Postion 属性可读写,支持形状变化后更新
- 可扩展性:虚方法允许子类自定义绘制和命中检测
- 代码复用:基类提供了80%的通用功能
这个基类大大简化了控制点的实现,添加新类型控制点只需继承并重写少量方法!
更多推荐



所有评论(0)