-
前言
- 本文描述了如何使用Winform窗体中的控件,绘制形状(如:矩形),实现添加、删除、选中、移动、缩放。点击按钮添加矩形。
- WinForms (Windows Forms) 是微软.NET框架中的一个图形用户界面(GUI)类库,用于开发Windows桌面应用程序。
- 在创建窗体后可以通过拖拽方式创建各种控件(如文本框按钮等)。
-
开发环境
- 操作系统:Win11
- 编程软件:Visual Studio 2022
- 其他相关:.Net Framework 4.6.0
-
文章内容
- 描述如何使用WinForm实现绘制形状图形,可以使用在任意Control。
- 1、绘制矩形、不透明、透明、半透明。
- 2、按住拖动、点击选中、滚轮缩放。
- 3、传入控件对象作为参数即可在该控件中绘制。
-
演示案例
-
代码讲解
-
Shape类
-
创建形状的基本属性:
- ID(方便选中)
- 颜色、线宽、透明度(alpha)
- 矩形手柄(调整大小)、选中状态。
-
创建形状的基本方法(抽象方法):
- 1、判断点是否在手柄上;
- 2、判断显示手柄方向类型;
- 3、绘制矩形;
- 4、绘制手柄。
public abstract class Shape { #region 字段、属性 private int _id = -1; private Color _shapeColor = Color.Blue; //填充颜色 private Color _handleColor = Color.Blue; //边框颜色 private float _borderWidth = 0; //边缘宽度 private int _alpha = 50; // 透明度 private int _handleSize = 8; // 手柄的视觉大小 private int _handleOutSize = 16; // 手柄外区域大小(比视觉大) private bool _isSelected = true; //选中状态 private static int _nextId = 0; public int ID { get => _id; private set => _id = value; } public Color ShapeColor { get => _shapeColor; set => _shapeColor = value; } public Color HandleColor { get => _handleColor; set => _handleColor = value; } public float BorderWidth { get => _borderWidth; set => _borderWidth = value; } public int Alpha { get => _alpha; set => _alpha = value; } public int HandleSize { get => _handleSize; set { if (_handleSize != value) { _handleSize = value; HandleOutSize = value * 2; } } } public int HandleOutSize { get => _handleOutSize; set { if (_handleOutSize != value) { _handleOutSize = value; _handleSize = value / 2; } } } public bool IsSelected { get => _isSelected; set => _isSelected = value; } #endregion #region 构造函数 protected Shape() { //获取不重复的ID,当前程序运行生效。 ID = Interlocked.Increment(ref _nextId); } #endregion #region 抽象方法 /// <summary> /// 点是否在手柄上 /// </summary> public abstract bool IsInHandle(Point point, int x, int y); /// <summary> /// 获取手柄类型 /// </summary> public abstract HandleType GetHandleType(Point point); /// <summary> /// 绘制手柄 /// </summary> protected abstract void DrawHandle(Graphics graphics, int x, int y); /// <summary> /// 绘制形状和手柄 /// </summary> public abstract void DrawShapeWithHandles(Graphics graphics); #endregion }
-
-
URectangle 类
- 继承Shape类,方便统一形状。
-
创建矩形的基本属性:
- 矩形的基本属性参数(X,Y,Width,Height)。
- Rectangle 实际显示的矩形,RectangleF :用于缩放转换矩形
-
重写方法:
- 重写形状方法,实现具体的绘制图形功能。
public class URectangle: Shape { #region 字段|属性 private Rectangle rectangle; public Rectangle Rectangle { get => rectangle; set => rectangle = value; } private RectangleF rectangleF; public RectangleF RectangleF { get => rectangleF; set => rectangleF = value; } public int X { get { return rectangle.X; } set { if (rectangle != null) rectangle.X = value; } } public int Y { get { return rectangle.Y; } set { if (rectangle != null) rectangle.Y = value; } } public Point Point { get => new Point(X, Y); set { X = value.X; Y = value.Y; } } public int Width { get => rectangle.Width; set { if (rectangle!=null) { rectangle.Width = value; } } } public int Height { get => rectangle.Height; set { if (rectangle != null) { rectangle.Height = value; } } } public Size Location { get => new Size(Width, Width); set { Width = value.Width; Width = value.Width; } } #endregion #region 构造函数 public URectangle():base() { } public URectangle(Rectangle rectangle) { this.rectangle = rectangle; } #endregion #region 绘制矩形相关 /// <summary> /// 绘制矩形和手柄(选中时绘制): /// </summary> public override void DrawShapeWithHandles(Graphics graphics) { using (var brush = new SolidBrush(Color.FromArgb(Alpha, ShapeColor))) { graphics.FillRectangle(brush, rectangle); } Pen pen = new Pen(ShapeColor, BorderWidth); graphics.DrawRectangle(pen, rectangle); if (IsSelected) { DrawHandle(graphics, rectangle.Left, rectangle.Top); // 左上角 DrawHandle(graphics, rectangle.Right, rectangle.Top); // 右上角 DrawHandle(graphics, rectangle.Left, rectangle.Bottom); // 左下角 DrawHandle(graphics, rectangle.Right, rectangle.Bottom); // 右下角 } } /// <summary> /// 绘制手柄: /// </summary> protected override void DrawHandle(Graphics graphics, int x, int y) { Rectangle handleRect = new Rectangle( x - HandleSize / 2,y - HandleSize / 2,HandleSize,HandleSize); using (var brush = new SolidBrush(Color.FromArgb(Alpha, HandleColor))) { graphics.FillRectangle(brush, handleRect); } Pen pen = new Pen(HandleColor, BorderWidth); graphics.DrawRectangle(pen, handleRect); } /// <summary> /// 获取要显示的手柄类型:根据指定点,判断当前矩形应该使用什么类型的手柄。 /// </summary> public override HandleType GetHandleType(Point point) { if (IsInHandle(point, rectangle.Left, rectangle.Top)) return HandleType.TopLeft; if (IsInHandle(point, rectangle.Right, rectangle.Top)) return HandleType.TopRight; if (IsInHandle(point, rectangle.Left, rectangle.Bottom)) return HandleType.BottomLeft; if (IsInHandle(point, rectangle.Right, rectangle.Bottom)) return HandleType.BottomRight; return HandleType.None; } /// <summary> /// 指定点是否在手柄范围内 /// </summary> public override bool IsInHandle(Point point, int x, int y) { Rectangle handleRect = new Rectangle(x - HandleOutSize / 2,y - HandleOutSize / 2,HandleOutSize,HandleOutSize); return handleRect.Contains(point); } #endregion }
-
ShapeModule 类 -
-
创建URectangle 类对象只能创建基本的形状,具体还需要将形状图形绘制到控件中,下面的模块就是如何实现该绘制形状到控件容器中。
-
通过创建事件、并给容器绑定事件,然后根据事件操作判断实现功能:
- Container_MouseDown :判断执行绘制、选中
- Container_MouseMove :判断执行拖动、调整大小
- Container_MouseUp :判断执行绘制
- Container_Paint :判断执行绘制形状
- Container_MouseWheel:判断执行拖放
public class ShapeModule { #region 字段 | 属性 private float scaleX = 1.0f; //缩放X private float scaleY = 1.0f; //缩放Y private bool isScaled = false; //是否缩放 private int selectedIndex = -1; //选择索引 private DrawMode currentMode = DrawMode.None; //当前模式 private HandleType handleType = HandleType.None; //手柄类型 private Point startPoint; //起始点 private Control container; //形状显示容器 private URectangle currentRect; //当前矩形 private URectangle displayRect; //选择的矩形 private ContextMenuStrip rightKeyMenuStrip; //右键菜单 private Dictionary<int, Shape> _shapeDictionary; //形状存储字典 public float ScaleX { get => scaleX; set { scaleX = value; UpdateDisplayShape(); } } public float ScaleY { get => scaleY; set { scaleY = value; UpdateDisplayShape(); } } public DrawMode CurrentMode { get => currentMode; set => currentMode = value; } public Dictionary<int, Shape> ShapeDictionary { get => _shapeDictionary;private set => _shapeDictionary = value; } #endregion #region 构造函数 public ShapeModule(Control container) { Initialize(container); } #endregion #region 初始化 private void Initialize(Control container) { this.container = container; ShapeDictionary = new Dictionary<int, Shape>(); InitializeContainer(); InitializeRightKeyMenu(); } #region 初始化容器 private void InitializeContainer() { container.MouseDown += Container_MouseDown; container.MouseUp += Container_MouseUp; container.MouseMove += Container_MouseMove; container.MouseUp += Container_MouseUp; container.MouseWheel += Container_MouseWheel; container.Paint += Container_Paint; } #endregion #region 右键菜单功能 private void InitializeRightKeyMenu() { // 创建菜单项 rightKeyMenuStrip = new ContextMenuStrip(); var copyItem = new ToolStripMenuItem("复制"); copyItem.Click += (s, e) => CopyAction(); var deleteItem = new ToolStripMenuItem("删除"); deleteItem.Click += (s, e) => DeleteAction(); // 创建右键菜单 rightKeyMenuStrip.Items.Add(copyItem); rightKeyMenuStrip.Items.Add(deleteItem); } private void CopyAction() { } private void DeleteAction() { ShapeDictionary.Remove(selectedIndex); selectedIndex = -1; container.Invalidate(); } #endregion #endregion #region 形状显示相关:缩放、显示 /// <summary> /// 更新显示形状 /// </summary> private void UpdateDisplayShape() { foreach (var shape in ShapeDictionary.Values) { if (shape is URectangle rectangle) { rectangle.Rectangle = new Rectangle( (int)(rectangle.RectangleF.X * scaleX), (int)(rectangle.RectangleF.Y * scaleY), (int)(rectangle.RectangleF.Width * scaleX), (int)(rectangle.RectangleF.Height * scaleY)); } } container.Invalidate(); } /// <summary> /// 缩小 /// </summary> private RectangleF ScaleDown(Rectangle rect) { return new RectangleF( rect.X / scaleX, rect.Y / scaleY, rect.Width / scaleX, rect.Height / scaleY); } /// <summary> /// 放大 /// </summary> private Rectangle ScaleUp(RectangleF rect) { return new Rectangle( (int)(rect.X * scaleX), (int)(rect.Y * scaleY), (int)(rect.Width * scaleX), (int)(rect.Height * scaleY)); } /// <summary> /// 放大 /// </summary> private void ZoomIn() { ScaleX += 0.1F; ScaleY += 0.1F; } /// <summary> /// 缩小 /// </summary> private void ZoomOut() { ScaleX -= 0.1F; ScaleY -= 0.1F; } #endregion #region 事件 private void Container_MouseDown(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { // 形状绘制 if (CurrentMode == DrawMode.Drawing) { currentRect = new URectangle(); startPoint = e.Location; currentRect.Rectangle = new Rectangle(e.Location, Size.Empty); } // 形状移动 else if (CurrentMode == DrawMode.Moving) { int dx = e.X - startPoint.X; int dy = e.Y - startPoint.Y; displayRect.X += dx; displayRect.Y += dy; displayRect.RectangleF = ScaleDown(displayRect.Rectangle); startPoint = e.Location; } // 形状调整大小 else if (CurrentMode == DrawMode.Resizing) { Rectangle rect = displayRect.Rectangle; switch (handleType) { case HandleType.TopLeft: rect.X = e.X; rect.Y = e.Y; rect.Width = displayRect.Rectangle.Right - e.X; rect.Height = displayRect.Rectangle.Bottom - e.Y; break; case HandleType.TopRight: rect.Y = e.Y; rect.Width = e.X - displayRect.Rectangle.Left; rect.Height = displayRect.Rectangle.Bottom - e.Y; break; case HandleType.BottomLeft: rect.X = e.X; rect.Width = displayRect.Rectangle.Right - e.X; rect.Height = e.Y - displayRect.Rectangle.Top; break; case HandleType.BottomRight: rect.Width = e.X - displayRect.Rectangle.Left; rect.Height = e.Y - displayRect.Rectangle.Top; break; } // 确保宽度和高度不为负 if (rect.Width > 0 && rect.Height > 0) { displayRect.RectangleF = ScaleDown(rect); displayRect.Rectangle = rect; } } // 形状判断选择 else { selectedIndex = -1; foreach (var shape in ShapeDictionary.Values) { if (shape is URectangle uRectangle) { if (uRectangle.Rectangle.Contains(e.Location) || uRectangle.GetHandleType(e.Location) != HandleType.None) { selectedIndex = uRectangle.ID; displayRect = uRectangle; startPoint = e.Location; handleType = uRectangle.GetHandleType(e.Location); CurrentMode = handleType != HandleType.None ? DrawMode.Resizing : DrawMode.Moving; break; } } } } } //右键菜单 if (e.Button == MouseButtons.Right) { if (selectedIndex != -1) { rightKeyMenuStrip.Show(container, e.Location); } } container.Invalidate(); } private void Container_MouseMove(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (CurrentMode == DrawMode.Drawing) { int x = Math.Min(startPoint.X, e.X); int y = Math.Min(startPoint.Y, e.Y); int width = Math.Abs(startPoint.X - e.X); int height = Math.Abs(startPoint.Y - e.Y); currentRect.Rectangle = new Rectangle(x, y, width, height); } else if (CurrentMode == DrawMode.Moving && selectedIndex >= 0) { int dx = e.X - startPoint.X; int dy = e.Y - startPoint.Y; displayRect.X += dx; displayRect.Y += dy; ShapeDictionary[selectedIndex] = displayRect; displayRect.RectangleF = ScaleDown(displayRect.Rectangle); startPoint = e.Location; } else if (CurrentMode == DrawMode.Resizing && selectedIndex >= 0) { Rectangle rect = displayRect.Rectangle; switch (handleType) { case HandleType.TopLeft: rect.X = e.X; rect.Y = e.Y; rect.Width = displayRect.Rectangle.Right - e.X; rect.Height = displayRect.Rectangle.Bottom - e.Y; break; case HandleType.TopRight: rect.Y = e.Y; rect.Width = e.X - displayRect.Rectangle.Left; rect.Height = displayRect.Rectangle.Bottom - e.Y; break; case HandleType.BottomLeft: rect.X = e.X; rect.Width = displayRect.Rectangle.Right - e.X; rect.Height = e.Y - displayRect.Rectangle.Top; break; case HandleType.BottomRight: rect.Width = e.X - displayRect.Rectangle.Left; rect.Height = e.Y - displayRect.Rectangle.Top; break; } if (rect.Width > 0 && rect.Height > 0) { displayRect.Rectangle = rect; displayRect.RectangleF = ScaleDown(rect); ShapeDictionary[selectedIndex] = displayRect; } } } else { if (selectedIndex >= 0) { handleType = displayRect.GetHandleType(e.Location); switch (handleType) { case HandleType.TopLeft: case HandleType.BottomRight: container.Cursor = Cursors.SizeNWSE; break; case HandleType.TopRight: case HandleType.BottomLeft: container.Cursor = Cursors.SizeNESW; break; default: container.Cursor = displayRect.Rectangle.Contains(e.Location) ? Cursors.SizeAll : Cursors.Default; break; } } else { container.Cursor = Cursors.Default; } } container.Invalidate(); } private void Container_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) { if (CurrentMode == DrawMode.Drawing && currentRect.Width > 0 && currentRect.Height > 0) { currentRect.RectangleF = ScaleDown(currentRect.Rectangle); ShapeDictionary.Add(currentRect.ID, currentRect); selectedIndex = currentRect.ID; displayRect = currentRect; startPoint = e.Location; handleType = currentRect.GetHandleType(e.Location); CurrentMode = handleType != HandleType.None ? DrawMode.Resizing : DrawMode.Moving; } CurrentMode = DrawMode.None; container.Invalidate(); } } private void Container_Paint(object sender, PaintEventArgs e) { foreach (var shape in ShapeDictionary.Values) { if (shape.ID == selectedIndex) { shape.IsSelected = true; shape.ShapeColor = Color.Red; shape.HandleColor = Color.White; } else { shape.IsSelected = false; shape.ShapeColor = Color.Blue; shape.HandleColor = Color.White; } shape.DrawShapeWithHandles(e.Graphics); } if (currentRect == null) return; if (CurrentMode == DrawMode.Drawing) { using (var brush = new SolidBrush(Color.FromArgb(50, 0, 0, 255))) { e.Graphics.FillRectangle(brush, currentRect.Rectangle); } e.Graphics.DrawRectangle(Pens.Blue, currentRect.Rectangle); } } private void Container_MouseWheel(object sender, MouseEventArgs e) { if (e.Delta > 0) ZoomOut(); else if (e.Delta < 0) ZoomIn(); } #endregion }
-
-
用到的枚举
/// <summary> /// 绘制模式 /// </summary> public enum DrawMode { None, Drawing, Moving, Resizing, } /// <summary> /// 手柄类型 /// </summary> public enum HandleType { None, TopLeft, TopRight, BottomLeft, BottomRight }
-
用户界面代码
using CustomControls; using Shapes; using System; using System.Drawing; namespace CS学习之绘制形状 { public partial class MainForm : WinFormBase { ShapeModule shapeModule; string imageFile = AppDomain.CurrentDomain.BaseDirectory + "source.png"; public MainForm() { InitializeComponent(); this.CenterToParent(); shapeModule = new ShapeModule(uc_Canvas); uc_Canvas.Image = Image.FromFile(imageFile); } private void btn_Draw_Click(object sender, System.EventArgs e) { shapeModule.CurrentMode = DrawMode.Drawing; } } }
-
双缓存图像控件
public class UImageControl : PictureBox { public UImageControl() { this.DoubleBuffered = true; SizeMode = PictureBoxSizeMode.StretchImage; } }
-
用户界面
-
用户界面
- 该用户界面为前几期的内容创建,如想使用可翻阅前面的内容查看代码。
- 不使用该窗体也可以使用自定义的Form窗体,只需添加按钮,添加任意控件即可,注意设置Dock属性为Fill。
- 控件最好设置双缓冲,否则会出现界面闪烁。
- 设计缺陷:当用户自定义控件绘制图像时,如果也创建了下面实现的事件如鼠标按下事件(MouseDown),可能会起冲突。
DoubleBuffered = true; //启用双缓冲
-
结语
- 使用方法,将自定义控件创建在同一个命名空间下,点击生成无报错后,即可在工具箱中查看选择自定义控件。
- 拖拽到当前窗体即可。如果是创建自己的类库,并引用这个类库,引用时可能得报错原因,目标框架不同。
- 公众号:编程笔记in
-
最后
- 如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!
- 如有疑问,欢迎评论区留言。
- 也可以关注微信公众号 [编程笔记in] 社区一起交流心得!
- 项目源码:gitee.com/li503560604/cshape-demos