IHandle
·
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 |
属性 | 获取鼠标悬停时的光标样式 |
核心设计思想:
- 统一接口:所有控制点都实现相同的接口
- 自包含:每个控制点知道自己的位置、绘制方式、交互行为
- 可扩展:添加新类型的控制点只需实现 IHandle 接口
这个接口是形状编辑系统的关键抽象,让形状可以拥有各种可拖拽的控制点,实现丰富的交互功能!
更多推荐




所有评论(0)