IHandleShape
IHandleShape 和 HandleShapeBase 详解 - 带控制点的形状这是形状系统的控制点手柄功能实现让形状可以显示可拖拽的控制点用于缩放、旋转、移动等交互操作是高级图形编辑工具的核心功能。 文件头部和引用// 版权信息同前// Copyright (c) HeBianGu Authors. All Rights Reserved.// ...省略usingH.LabelImg.ShapeBox.Shapes.Handles;// 导入控制点相关的类如 IHandle、各种手柄namespaceH.LabelImg.ShapeBox.Shapes.Base;// 命名空间存放形状的基类 IHandleShape 接口// 接口定义表示一个带有控制点手柄的形状// 继承自 IShape所以它也是一个可以绘制的形状publicinterfaceIHandleShape:IShape{// 是否显示控制点// true 显示控制点如选中时显示拖拽手柄// false 不显示控制点boolUseHandle{get;set;}// 命中检测判断某个点是否击中了某个控制点// 参数// view - 视图对象// position - 要检测的点通常是鼠标坐标// 返回值// 被击中的控制点对象如果没有击中则返回 nullIHandleHitIHandle(IViewview,Pointposition);}控制点Handle是什么控制点是形状周围的小图形元素小方块、小圆圈等用户可以通过拖拽它们来编辑形状。选中一个矩形后显示的控制点 控制点8个 ●───────●───────● │ │ │ │ ● ● │ │ │ │ ●───────●───────● 四个角改变大小等比例 四边中点改变大小单向拉伸️ HandleShapeBase 抽象类// 抽象基类为带控制点的形状提供默认实现// 继承自 SelectableShapeBase已有选中功能// 实现 IHandleShape 接口publicabstractclassHandleShapeBase:SelectableShapeBase,IHandleShape{// 是否显示控制点默认 falsepublicboolUseHandle{get;set;}// 重写绘制方法publicoverridevoidDrawing(IViewview,DrawingContextdc,Penpen,Brushfillnull){// 如果需要显示控制点if(this.UseHandle)// 绘制控制点this.DrawHandle(view,dc,pen,fill);// 注意这里没有调用基类的 Drawing// 意味着如果 UseHandle true只绘制控制点不绘制形状本身// 这可能是一个设计上的考虑形状本身由其他方式绘制}// 绘制控制点// virtual 子类可以重写自定义控制点的绘制方式publicvirtualvoidDrawHandle(IViewview,DrawingContextdrawingContext,Penpen,Brushfillnull){// 遍历所有控制点foreach(variteminthis.CreateHandles()){// 绘制每个控制点item.Draw(view,drawingContext,pen,fill);}}// 创建控制点集合// protected 只有子类可以访问// virtual 子类必须重写提供具体的控制点protectedvirtualIEnumerableIHandleCreateHandles(){// 默认返回空集合没有控制点returnEnumerable.EmptyIHandle();}// 命中检测判断是否击中了某个控制点publicvirtualIHandleHitIHandle(IViewview,Pointposition){// 如果不显示控制点直接返回 nullif(this.UseHandlefalse)returnnull;// 遍历所有控制点foreach(varhandleinthis.CreateHandles()){// 如果点中了这个控制点if(handle.HitTestPoint(view,position))returnhandle;// 返回被点中的控制点}// 没有点中任何控制点returnnull;}} 继承关系图IShape接口 ↓ IHitableShape接口 ↓ IMouseOverShape接口 ↓ ISelectableShape接口 ↓ IHandleShape接口← 当前接口 ↓ ShapeBase抽象类 ↓ CommonShapeBase抽象类 ↓ MouseOverShapeBase抽象类 ↓ SelectableShapeBase抽象类 ↓ HandleShapeBase抽象类← 当前类 ↓ PreviewShapeBase抽象类 ↓ 具体带控制点的形状 ├── HandleRectangle带控制点的矩形 ├── HandleCircle带控制点的圆形 ├── HandlePolygon带控制点的多边形 └── ... 更多 控制点IHandle接口示例// 控制点接口假设的定义publicinterfaceIHandle{// 绘制控制点voidDraw(IViewview,DrawingContextdc,Penpen,Brushfill);// 命中检测boolHitTestPoint(IViewview,Pointpoint);// 获取控制点的位置PointGetPosition();// 更新形状当拖拽控制点时voidUpdateShape(IShapeshape,Vectordelta);// 控制点类型左上角、右上角、中心等HandleTypeType{get;}} 完整实现示例带控制点的矩形publicclassHandleRectangle:HandleShapeBase{publicRectBounds{get;set;}// 重写提供控制点protectedoverrideIEnumerableIHandleCreateHandles(){// 返回8个控制点yieldreturnnewResizeHandle(this,HandleType.TopLeft);// 左上角yieldreturnnewResizeHandle(this,HandleType.TopCenter);// 上边中点yieldreturnnewResizeHandle(this,HandleType.TopRight);// 右上角yieldreturnnewResizeHandle(this,HandleType.MiddleLeft);// 左边中点yieldreturnnewResizeHandle(this,HandleType.MiddleRight);// 右边中点yieldreturnnewResizeHandle(this,HandleType.BottomLeft);// 左下角yieldreturnnewResizeHandle(this,HandleType.BottomCenter);// 下边中点yieldreturnnewResizeHandle(this,HandleType.BottomRight);// 右下角}// 绘制形状本身publicoverridevoidDrawing(IViewview,DrawingContextdc,Penpen,Brushfill){// 绘制矩形dc.DrawRectangle(fill,pen,Bounds);// 调用基类绘制控制点如果 UseHandle truebase.Drawing(view,dc,pen,fill);}}// 控制点实现示例publicclassResizeHandle:IHandle{privateHandleRectangle_owner;privateHandleType_type;privatedouble_handleSize8;// 控制点大小publicResizeHandle(HandleRectangleowner,HandleTypetype){_ownerowner;_typetype;}publicvoidDraw(IViewview,DrawingContextdc,Penpen,Brushfill){PointpositionGetPosition();doublehalf_handleSize/2;// 绘制小矩形作为控制点dc.DrawRectangle(fill??Brushes.White,pen,newRect(position.X-half,position.Y-half,_handleSize,_handleSize));}publicPointGetPosition(){varbounds_owner.Bounds;switch(_type){caseHandleType.TopLeft:returnnewPoint(bounds.Left,bounds.Top);caseHandleType.TopCenter:returnnewPoint(bounds.Leftbounds.Width/2,bounds.Top);caseHandleType.TopRight:returnnewPoint(bounds.Right,bounds.Top);caseHandleType.MiddleLeft:returnnewPoint(bounds.Left,bounds.Topbounds.Height/2);caseHandleType.MiddleRight:returnnewPoint(bounds.Right,bounds.Topbounds.Height/2);caseHandleType.BottomLeft:returnnewPoint(bounds.Left,bounds.Bottom);caseHandleType.BottomCenter:returnnewPoint(bounds.Leftbounds.Width/2,bounds.Bottom);caseHandleType.BottomRight:returnnewPoint(bounds.Right,bounds.Bottom);default:returnnewPoint(0,0);}}publicboolHitTestPoint(IViewview,Pointpoint){PointpositionGetPosition();doublehalf_handleSize/2;// 检测点是否在控制点矩形内returnMath.Abs(point.X-position.X)halfMath.Abs(point.Y-position.Y)half;}publicvoidUpdateShape(IShapeshape,Vectordelta){varrect_owner.Bounds;switch(_type){caseHandleType.TopLeft:rectnewRect(rect.Leftdelta.X,rect.Topdelta.Y,rect.Width-delta.X,rect.Height-delta.Y);break;caseHandleType.TopRight:rectnewRect(rect.Left,rect.Topdelta.Y,rect.Widthdelta.X,rect.Height-delta.Y);break;// ... 其他类型的处理}_owner.Boundsrect;}publicHandleTypeType_type;}publicenumHandleType{TopLeft,TopCenter,TopRight,MiddleLeft,MiddleRight,BottomLeft,BottomCenter,BottomRight,Center// 移动用} 在标注工具中的使用在 SelectShapeBox 中处理控制点拖拽publicclassSelectShapeBox:MouseOverShapeBox{privateIHandle_selectedHandle;privatePoint_dragStart;protectedoverridevoidOnMouseDown(MouseButtonEventArgse){Pointpointe.GetPosition(this);// 1. 先检查是否点中了控制点foreach(varshapeinShapes.OfTypeIHandleShape()){varhandleshape.HitIHandle(this,point);if(handle!null){_selectedHandlehandle;_dragStartpoint;return;}}// 2. 没有点中控制点再检查是否点中了形状// ... 原有的选中逻辑}protectedoverridevoidOnMouseMove(MouseEventArgse){if(_selectedHandle!null){Pointcurrente.GetPosition(this);Vectordeltacurrent-_dragStart;// 获取被拖拽控制点所属的形状varshapeGetShapeByHandle(_selectedHandle);// 更新形状_selectedHandle.UpdateShape(shape,delta);_dragStartcurrent;// 重绘this.DrawShapes();this.DrawSelectableShapes();}else{base.OnMouseMove(e);}}protectedoverridevoidOnMouseUp(MouseButtonEventArgse){_selectedHandlenull;base.OnMouseUp(e);}} 不同形状的控制点矩形的控制点8个●───────●───────● │ │ ● ● │ │ ●───────●───────●圆形的控制点4个方向 中心● │ ●───●───● │ ●多边形的控制点每个顶点●─────● / \ ● ● \ / ●─────● 使用示例// 创建带控制点的矩形varrectnewHandleRectangle{BoundsnewRect(100,100,200,150),UseHandletrue,// 显示控制点StrokeBrushes.Blue,StrokeThickness2};// 添加到标注框varshapeBoxnewSelectShapeBox();shapeBox.Shapes.Add(rect);// 选中形状通常选中时显示控制点shapeBox.SelectShapes(rect);rect.UseHandletrue;// 显示控制点// 用户可以拖拽控制点来调整矩形大小设计模式分析1. 工厂方法模式protectedvirtualIEnumerableIHandleCreateHandles(){returnEnumerable.EmptyIHandle();}子类通过重写这个方法来提供具体的控制点。2. 组合模式形状包含多个控制点可以统一管理。3. 策略模式不同形状有不同的控制点创建策略。4. 装饰器模式在选中功能的基础上增加控制点功能。总结组件类型职责IHandleShape接口定义控制点功能HandleShapeBase抽象类提供控制点的默认实现框架核心功能UseHandle控制是否显示控制点CreateHandles()子类提供具体的控制点集合HitIHandle()检测是否点中了控制点DrawHandle()绘制所有控制点这个设计让形状具备了可编辑能力用户可以通过拖拽控制点来调整形状的大小和形状是实现专业图形编辑器的关键