PreviewShapeBox

张开发
2026/4/18 0:24:52 15 分钟阅读

分享文章

PreviewShapeBox
PreviewShapeBoxPreviewShapeBox 源码详解 - 跟随鼠标的预览标注框控件这是一个支持鼠标跟随预览的图像标注控件可以在鼠标位置显示一个预览形状如矩形框常用于绘图工具中显示将要绘制的形状大小和位置。 文件头部和引用// 版权信息同前// Copyright (c) HeBianGu Authors. All Rights Reserved.// ...省略usingH.Extensions.Common;// 扩展方法ToEnumerable等usingSystem.Windows.Input;// 键盘/鼠标输入Mouse.GetPosition等 接口定义// 定义预览形状视图的能力publicinterfaceIPreviewShapeView{// 绘制预览形状参数可变数量的预览形状voidDrawPreviewShape(paramsIPreviewShape[]previewShapes);}通俗理解这是一个合同规定实现者能够在鼠标位置绘制预览形状比如显示一个半透明的矩形框。️ 类定义// 继承 StateShapeBox状态机实现 IPreviewShapeView预览视图// 功能层级ShapeBox → MouseOverShapeBox → SelectShapeBox → StateShapeBox → PreviewShapeBox// 基础显示 → 悬停高亮 → 选中高亮 → 状态机交互 → 鼠标跟随预览publicclassPreviewShapeBox:StateShapeBox,IPreviewShapeView继承关系图ShapeBox基础图片/形状显示 ↓ MouseOverShapeBox鼠标悬停高亮 ↓ SelectShapeBox选中高亮 ↓ StateShapeBox状态机交互 ↓ PreviewShapeBox鼠标跟随预览← 当前类 私有字段// 专门用来绘制预览形状的视觉对象// 例如跟随鼠标移动的矩形框预览privateDrawingVisual_PreviewShapeDrawingVisualnewDrawingVisual();完整图层结构从下到上┌─────────────────────────────────┐ │ 预览图层PreviewShapeDrawingVisual│ ← 跟随鼠标的预览最上层 ├─────────────────────────────────┤ │ 状态图层StateShapeDrawingVisual│ ← 操作预览绘制中的形状 ├─────────────────────────────────┤ │ 选中图层SelectableShapeDrawingVisual│ ← 红色边框选中 ├─────────────────────────────────┤ │ 悬停图层MouseOverableShapeDrawingVisual│ ← 黄色边框悬停 ├─────────────────────────────────┤ │ 普通形状层ShapeDrawingVisual │ ← 蓝色边框普通 ├─────────────────────────────────┤ │ 图片层ImageDrawingVisual │ ← 背景图片 └─────────────────────────────────┘️ 重写创建视觉对象protectedoverrideIEnumerableVisualCreateVisuals(){// 在父类图层基础上添加预览图层// 父类返回[图片层, 普通形状层, 悬停图层, 选中图层, 状态图层]// 加上预览图层放在最上面显示在所有内容之上returnbase.CreateVisuals().Concat(this._PreviewShapeDrawingVisual.ToEnumerable());}为什么预览图层在最上面预览形状应该显示在所有现有形状之上让用户清楚地看到将要添加的形状。 预览边框颜色属性// 预览形状的边框颜色默认系统高亮色通常是蓝色publicBrushPreviewStroke{get{return(Brush)GetValue(PreviewStrokeProperty);}set{SetValue(PreviewStrokeProperty,value);}}publicstaticreadonlyDependencyPropertyPreviewStrokePropertyDependencyProperty.Register(PreviewStroke,typeof(Brush),typeof(PreviewShapeBox),newFrameworkPropertyMetadata(SystemColors.HighlightBrush,// 系统高亮色通常是蓝色(d,e){}));SystemColors.HighlightBrush系统主题的高亮颜色在不同Windows主题下会自动适配。 预览填充颜色属性// 预览形状的填充颜色默认黄绿色publicBrushPreviewFill{get{return(Brush)GetValue(PreviewFillProperty);}set{SetValue(PreviewFillProperty,value);}}publicstaticreadonlyDependencyPropertyPreviewFillPropertyDependencyProperty.Register(PreviewFill,typeof(Brush),typeof(PreviewShapeBox),newFrameworkPropertyMetadata(Brushes.Chartreuse,// 默认黄绿色半透明效果更好(d,e){})); 预览边框粗细属性// 预览形状的边框粗细默认1.0像素publicdoublePreviewStrokeThickness{get{return(double)GetValue(PreviewStrokeThicknessProperty);}set{SetValue(PreviewStrokeThicknessProperty,value);}}publicstaticreadonlyDependencyPropertyPreviewStrokeThicknessPropertyDependencyProperty.Register(PreviewStrokeThickness,typeof(double),typeof(PreviewShapeBox),newFrameworkPropertyMetadata(1.0,(d,e){})); 偏移量属性核心// 预览形状相对于鼠标的偏移量默认10像素publicdoubleOffset{get{return(double)GetValue(OffsetProperty);}set{SetValue(OffsetProperty,value);}}publicstaticreadonlyDependencyPropertyOffsetPropertyDependencyProperty.Register(Offset,typeof(double),typeof(PreviewShapeBox),newFrameworkPropertyMetadata(10.0,(d,e){}));Offset 的作用没有偏移 有偏移 ┌─────┐ 鼠标位置 │鼠标 │ ↓ └─────┘ ┌─────────┐ │ 预览框 │ └─────────┘ ↑ 偏移10像素为什么需要偏移避免预览框完全遮挡鼠标指针让用户能看到鼠标位置。✏️ 绘制预览形状核心方法// 实现 IPreviewShapeView 接口publicvoidDrawPreviewShape(paramsIPreviewShape[]previewShapes){// 打开预览图层的绘制上下文usingvardrawingContextthis._PreviewShapeDrawingVisual.RenderOpen();// 如果没有需要预览的形状直接返回清除之前的预览if(previewShapesnull||previewShapes.Length0)return;// 获取鼠标相对于当前控件的位置PointpointMouse.GetPosition(this);// 计算缩放后的偏移量// 缩放时偏移量也需要调整否则预览框会偏离鼠标太远doubleoffsetthis.Offset/this.Scale;// 应用坐标变换将后续绘制的内容平移到鼠标位置 偏移// PushTransform 压入变换栈应用变换drawingContext.PushTransform(newTranslateTransform(point.Xoffset,point.Yoffset));// 计算视图中的边框粗细考虑缩放doublestrokeThicknessthis.ToViewThickness(this.PreviewStrokeThickness);// 遍历每个预览形状foreach(varpreviewShapeinpreviewShapes){// 调用预览形状自己的绘制方法previewShape.DrawPreview(this,drawingContext,this.PreviewStroke,// 预览边框颜色strokeThickness,// 预览边框粗细this.PreviewFill,// 预览填充颜色offset);// 偏移量}// 弹出变换栈恢复原来的坐标系统drawingContext.Pop();}坐标变换详解// 正常绘制没有变换形状.Draw(0,0,100,100)→ 画在(0,0)位置// 应用平移变换后drawingContext.PushTransform(newTranslateTransform(50,50));形状.Draw(0,0,100,100)→ 实际画在(50,50)位置 drawingContext.Pop();// 恢复// 之后的绘制回到正常坐标为什么使用 Transform 而不是直接修改坐标预览形状不需要知道鼠标位置只管画自己变换会自动处理缩放、旋转等复杂情况代码更简洁职责分离 总体设计思路完整的控件继承体系FrameworkElement ↓ ShapeBox基础显示 ├── 图片显示 ├── 形状绘制 └── 缩放支持 ↓ MouseOverShapeBox悬停高亮 ├── 鼠标检测 ├── 悬停样式 └── IMouseOverShape ↓ SelectShapeBox选中高亮 ├── 选中样式 ├── 选中集合 └── ISelectableShape ↓ StateShapeBox状态机 ├── 状态图层 ├── 状态样式 ├── 鼠标事件路由 └── IViewState ↓ PreviewShapeBox预览← 当前类 ├── 预览图层 ├── 预览样式 ├── 鼠标跟随 ├── 偏移量控制 └── IPreviewShape预览形状的工作流程用户移动鼠标 ↓ 某个状态如 DrawRectangleState监听 MouseMove ↓ 调用 DrawPreviewShape() 方法 ↓ 获取鼠标位置Mouse.GetPosition ↓ 计算偏移量Offset / Scale ↓ 应用平移变换到鼠标偏移位置 ↓ 遍历预览形状调用 DrawPreview() ↓ 用户看到跟随鼠标的预览框 ↓ 鼠标继续移动 → 清除旧预览 → 绘制新预览完整使用示例// 创建预览标注框varboxnewPreviewShapeBox();box.ImageSourcemyImage;// 配置各种样式// 普通样式box.StrokeBrushes.Blue;box.StrokeThickness1;// 悬停样式box.MouseOverStrokeBrushes.Yellow;box.MouseOverStrokeThickness2;// 选中样式box.SelectStrokeBrushes.Red;box.SelectStrokeThickness3;// 状态预览样式绘制中的预览box.StateStrokeBrushes.Green;box.StateStrokeThickness1;// 鼠标跟随预览样式最上层box.PreviewStrokeBrushes.Blue;box.PreviewStrokeThickness2;box.PreviewFillBrushes.LightBlue;// 半透明浅蓝色box.Offset15;// 预览框偏移鼠标15像素// 创建一个预览矩形例如50x50的正方形varpreviewRectnewPreviewRectangleShape(50,50);// 在鼠标位置显示预览box.DrawPreviewShape(previewRect);// 或者在一个状态中持续更新publicclassDrawFixedRectState:IViewState{privatePreviewRectangleShape_previewnewPreviewRectangleShape(100,80);publicvoidMouseMove(StateShapeBoxview,MouseEventArgse){// 鼠标移动时更新预览位置if(viewisPreviewShapeBoxpreviewBox){previewBox.DrawPreviewShape(_preview);}}}预览形状接口IPreviewShapepublicinterfaceIPreviewShape:IShape{// 绘制预览效果voidDrawPreview(PreviewShapeBoxbox,DrawingContextdc,Brushstroke,doublethickness,Brushfill,doubleoffset);}// 示例固定大小的预览矩形publicclassPreviewRectangleShape:IPreviewShape{publicdoubleWidth{get;set;}publicdoubleHeight{get;set;}publicPreviewRectangleShape(doublewidth,doubleheight){Widthwidth;Heightheight;}publicvoidDrawPreview(PreviewShapeBoxbox,DrawingContextdc,Brushstroke,doublethickness,Brushfill,doubleoffset){// 注意坐标变换已经应用这里画在(0,0)即可// 实际显示位置 鼠标位置 偏移varrectnewRect(0,0,Width,Height);dc.DrawRectangle(fill,newPen(stroke,thickness),rect);}}五种预览形状示例预览类型形状用途固定大小矩形100x80添加固定尺寸的标注框可变大小矩形跟随鼠标拖拽绘制自定义大小的框圆形半径50px添加圆形标注十字线交叉线标记点箭头箭头形状指示方向设计模式识别装饰器模式层层添加功能预览是最外层装饰策略模式不同预览形状有不同绘制策略模板方法模式DrawPreviewShape定义绘制流程观察者模式鼠标移动触发预览更新状态模式不同状态下预览行为不同六种控件的功能对比功能ShapeBoxMouseOverSelectStatePreview显示图片✅✅✅✅✅绘制形状✅✅✅✅✅鼠标悬停高亮❌✅✅✅✅选中高亮❌❌✅✅✅状态机交互❌❌❌✅✅操作预览❌❌❌✅✅鼠标跟随预览❌❌❌❌✅偏移量控制❌❌❌❌✅实际应用场景场景1添加固定大小的标注框 ┌─────────────────────────────┐ │ 图片内容 │ │ ┌──────┐ │ │ 鼠标→● │预览框│ │ ← 跟随鼠标显示 │ └──────┘ │ └─────────────────────────────┘ 场景2绘制矩形工具 用户选择矩形工具后 鼠标变成十字线 鼠标位置显示100x80的预览框 点击后添加正式矩形 场景3智能辅助 根据鼠标下的内容自动推荐标注框大小 例如检测到人脸显示人脸框预览性能优化要点// 不要每帧都创建新的预览对象// ❌ 不好box.DrawPreviewShape(newPreviewRectangleShape(100,80));// ✅ 好复用同一个预览对象privatePreviewRectangleShape_previewnewPreviewRectangleShape(100,80);box.DrawPreviewShape(_preview);// 清除预览box.DrawPreviewShape();// 传空数组这个控件为绘图工具提供了优雅的鼠标跟随预览功能用户可以清楚地知道将要添加的形状位置和大小

更多文章