IPreviewShape

张开发
2026/4/10 12:06:30 15 分钟阅读

分享文章

IPreviewShape
IPreviewShape 和 PreviewShapeBase 详解 - 预览形状这是形状系统的预览功能实现用于在鼠标位置显示一个将要添加的形状的预览效果常见于绘图工具中的实时预览功能。 文件头部和命名空间namespaceH.LabelImg.ShapeBox.Shapes.Base;// 命名空间存放形状的基类 IPreviewShape 接口// 接口定义表示一个可以被预览的形状// 通常用于在鼠标位置显示将要添加的形状publicinterfaceIPreviewShape{// 绘制预览状态下的形状// 参数说明// view - 视图对象提供缩放等信息// drawingContext - WPF绘图上下文// stroke - 预览边框颜色// strokeThickness - 预览边框粗细// fill - 预览填充颜色// offset - 相对于鼠标的偏移量避免遮挡鼠标voidDrawPreview(IViewview,DrawingContextdrawingContext,Brushstroke,doublestrokeThickness1,Brushfillnull,doubleoffset0);}接口的作用IPreviewShape 为形状增加了预览能力让形状能够在实际添加之前先显示一个临时的预览效果。// 普通形状只有正式绘制publicinterfaceIShape{voidDraw(...);// 正式绘制}// 预览形状支持提前预览publicinterfaceIPreviewShape{voidDrawPreview(...);// 预览绘制}️ PreviewShapeBase 抽象类// 抽象基类为预览形状提供默认实现// 继承自 HandleShapeBase已有控制点功能// 实现 IPreviewShape 接口publicabstractclassPreviewShapeBase:HandleShapeBase,IPreviewShape{// 实现预览绘制// virtual 子类可以重写自定义预览效果publicvirtualvoidDrawPreview(IViewview,DrawingContextdrawingContext,Brushstroke,doublestrokeThickness1,Brushfillnull,doubleoffset0){// 注意offset 参数在当前实现中没有使用// 直接调用普通绘制方法 Draw()this.Draw(view,drawingContext,stroke,strokeThickness,fill);}}关键理解预览形状 普通形状至少在这个基类实现中// DrawPreview 直接调用了 Drawthis.Draw(view,drawingContext,stroke,strokeThickness,fill);这意味着预览时的样式和普通样式是一样的。如果希望预览有不同的样式如半透明子类可以重写publicoverridevoidDrawPreview(...){// 使用半透明填充varsemiTransparentFillnewSolidColorBrush(Colors.Red){Opacity0.3};// 或者调用基类但传入不同的参数base.DrawPreview(view,dc,stroke,thickness,semiTransparentFill,offset);} 继承关系图IShape接口 ↓ IMouseOverShape接口← 鼠标悬停 ↓ ISelectableShape接口← 选中 ↓ IHandleShape接口← 控制点 ↓ IPreviewShape接口← 预览 ← 当前接口 ↓ ShapeBase抽象类 ↓ MouseOverShapeBase抽象类 ↓ SelectableShapeBase抽象类 ↓ HandleShapeBase抽象类← 控制点功能 ↓ PreviewShapeBase抽象类← 当前类 ↓ 具体预览形状 ├── PreviewRectangle预览矩形 ├── PreviewCircle预览圆形 ├── PreviewPolygon预览多边形 └── ... 更多 四种绘制方法对比一个完整的预览形状可能有四种绘制方式方法用途调用时机典型样式Draw()普通绘制正常显示蓝色边框DrawMouseOver()悬停绘制鼠标经过黄色边框DrawSelect()选中绘制被选择时红色边框DrawPreview()预览绘制添加前预览半透明虚线 完整实现示例预览矩形形状publicclassPreviewRectangle:PreviewShapeBase,IBoundingBoxable{publicdoubleWidth{get;set;}100;// 默认宽度publicdoubleHeight{get;set;}80;// 默认高度// 边界框用于定位publicRectBoundingBoxnewRect(0,0,Width,Height);// 实现绘制逻辑publicoverridevoidDrawing(IViewview,DrawingContextdc,Penpen,Brushfill){// 注意这里画在 (0,0) 位置// 实际显示位置由 PreviewShapeBox 的变换决定dc.DrawRectangle(fill,pen,newRect(0,0,Width,Height));}// 可选自定义预览效果publicoverridevoidDrawPreview(IViewview,DrawingContextdc,Brushstroke,doublethickness,Brushfill,doubleoffset){// 使用虚线边框 半透明填充varpennewPen(stroke,thickness){DashStyleDashStyles.Dash// 虚线};pen.Freeze();varsemiFillfill?.Clone();if(semiFillisSolidColorBrushsolid){solid.Opacity0.3;// 30%透明度}this.Drawing(view,dc,pen,semiFill);}}预览圆形形状publicclassPreviewCircle:PreviewShapeBase{publicdoubleRadius{get;set;}50;publicoverridevoidDrawing(IViewview,DrawingContextdc,Penpen,Brushfill){// 圆心在 (0,0)半径 Radiusdc.DrawEllipse(fill,pen,newPoint(0,0),Radius,Radius);}}预览十字线无填充publicclassPreviewCrosshair:PreviewShapeBase{publicdoubleSize{get;set;}20;publicoverridevoidDrawing(IViewview,DrawingContextdc,Penpen,Brushfill){// 绘制十字线varhalfSize/2;dc.DrawLine(pen,newPoint(-half,0),newPoint(half,0));dc.DrawLine(pen,newPoint(0,-half),newPoint(0,half));}} 在 PreviewShapeBox 中的使用回顾之前的PreviewShapeBox代码publicclassPreviewShapeBox:StateShapeBox,IPreviewShapeView{publicvoidDrawPreviewShape(paramsIPreviewShape[]previewShapes){usingvardcthis._PreviewShapeDrawingVisual.RenderOpen();if(previewShapesnull||previewShapes.Length0)return;// 获取鼠标位置PointpointMouse.GetPosition(this);// 计算偏移量doubleoffsetthis.Offset/this.Scale;// 应用平移变换到鼠标位置dc.PushTransform(newTranslateTransform(point.Xoffset,point.Yoffset));// 计算边框粗细doublethicknessthis.ToViewThickness(this.PreviewStrokeThickness);foreach(varpreviewShapeinpreviewShapes){// 调用预览绘制方法previewShape.DrawPreview(this,dc,this.PreviewStroke,// 预览边框颜色thickness,// 预览边框粗细this.PreviewFill,// 预览填充颜色offset);// 偏移量}dc.Pop();}}工作流程1. 用户移动鼠标 ↓ 2. 某个状态如 DrawRectangleState调用 DrawPreviewShape() ↓ 3. 传入 PreviewRectangle 对象 ↓ 4. PreviewShapeBox 设置坐标变换平移到鼠标位置 ↓ 5. 调用 PreviewRectangle.DrawPreview() ↓ 6. 在鼠标位置显示预览矩形 offset 参数的作用虽然PreviewShapeBase没有使用offset参数但它有重要作用// 在 PreviewShapeBox 中dc.PushTransform(newTranslateTransform(point.Xoffset,point.Yoffset));// ↑ offset 已经在这里应用了// 形状不需要再处理偏移// 所以 PreviewShapeBase 不需要再使用 offset 参数publicvirtualvoidDrawPreview(...,doubleoffset0){// offset 在这里被忽略因为变换已经处理了this.Draw(...);}offset 的作用让预览框不遮挡鼠标指针没有 offset 有 offset 10 鼠标位置 ● 鼠标位置 ● ┌─────────┐ 偏移10像素 │ 预览框 │ ┌─────────┐ │ 挡住鼠标│ │ 预览框 │ └─────────┘ │ 不挡鼠标 │ └─────────┘ 自定义预览效果示例带旋转的预览publicclassRotatingPreviewRect:PreviewRectangle{publicdoubleAngle{get;set;}45;publicoverridevoidDrawPreview(IViewview,DrawingContextdc,Brushstroke,doublethickness,Brushfill,doubleoffset){// 保存当前变换dc.PushTransform(newRotateTransform(Angle));// 调用基类绘制base.DrawPreview(view,dc,stroke,thickness,fill,offset);// 恢复变换dc.Pop();}}带阴影的预览publicclassShadowPreviewRect:PreviewRectangle{publicoverridevoidDrawPreview(IViewview,DrawingContextdc,Brushstroke,doublethickness,Brushfill,doubleoffset){// 先绘制阴影varshadowPennewPen(Brushes.Gray,thickness);varshadowRectnewRect(5,5,Width,Height);// 偏移5像素dc.DrawRectangle(Brushes.Transparent,shadowPen,shadowRect);// 再绘制实际矩形base.DrawPreview(view,dc,stroke,thickness,fill,offset);}} 完整功能层次┌─────────────────────────────────────────────────────┐ │ IShape (接口) │ │ Draw() │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ ShapeBase (抽象类) │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ MouseOverShapeBase (抽象类) │ │ DrawMouseOver() │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ SelectableShapeBase (抽象类) │ │ DrawSelect() │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ HandleShapeBase (抽象类) │ │ 控制点功能 │ └─────────────────────┬───────────────────────────────┘ │ ┌─────────────────────▼───────────────────────────────┐ │ PreviewShapeBase (抽象类) ← 当前类 │ │ DrawPreview() │ └─────────────────────────────────────────────────────┘ 使用示例// 创建预览标注框varboxnewPreviewShapeBox();box.ImageSourcemyImage;// 配置预览样式box.PreviewStrokeBrushes.Green;box.PreviewStrokeThickness2;box.PreviewFillnewSolidColorBrush(Colors.Green){Opacity0.3};box.Offset15;// 偏移15像素// 创建预览形状varpreviewRectnewPreviewRectangle{Width120,Height90};// 在鼠标位置显示预览通常在鼠标移动事件中box.DrawPreviewShape(previewRect);// 用户点击后添加正式形状box.Shapes.Add(newRectangleShape{BoundsnewRect(mousePosition.X,mousePosition.Y,120,90)});// 清除预览box.DrawPreviewShape();// 传空数组总结组件类型职责IPreviewShape接口定义预览绘制能力PreviewShapeBase抽象类提供预览绘制的默认实现直接复用普通绘制核心设计思想预览是普通形状的变体默认情况下预览样式与普通样式相同可自定义子类可以重写DrawPreview实现不同的预览效果坐标变换实际位置由PreviewShapeBox通过变换控制形状本身画在 (0,0)偏移量处理偏移由变换层处理形状不需要关心这种设计让添加新预览形状变得非常简单只需要继承PreviewShapeBase并实现Drawing方法即可

更多文章