C# GDI+ 在窗体与 Chart 控件上实现鼠标绘制矩形
本文讲解如何在 C# WinForms 应用程序中利用 GDI+ 实现实时鼠标绘图。通过捕获 MouseDown、MouseMove 和 MouseUp 事件,演示了两种典型场景:一是在主窗体背景上绘制动态矩形,二是在 System.Windows.Forms.DataVisualization.Charting.Chart 控件的数据绘图区覆盖绘制矩形。重点解析了 Graphics 对象的获取时机、坐标转换逻辑以及在 Chart 控件上绘图时需调用 Refresh() 而非 Clear() 的原因及具体代码实现。
结果或目标
实现一个基于 C# Windows Forms 的交互功能:用户按住鼠标左键并拖动时,系统会在屏幕上(窗体空白处或图表区域)实时跟随鼠标显示一个红色矩形框;松开鼠标后,矩形框停止跟随,状态归位。该功能常用于可视化选区、裁剪预览或图表测量。
准备条件
- 开发环境:Visual Studio,项目类型为 Windows Forms App (.NET Framework 或 .NET Core / .NET 5+)。
- 依赖命名空间:
System.Drawing(核心绘图对象)、System.Windows.Forms(UI控件)、System.Windows.Forms.DataVisualization(Chart 控件,若在窗体中引入)。 - 关键控件:默认
Form窗体及Chart控件实例(需手动添加引用并拖入设计器)。
真实操作顺序
第1步:初始化全局变量与图形上下文
目的:提前准备好用于存储绘图坐标的状态变量,并在窗口加载前获取可用于绘制的 Graphics 句柄。
入口:窗体构造函数 (InitializeComponent之后)。
动作与参数:
- 定义全局布尔值
begin标记是否正在绘制。 - 定义两个
Point结构体firstPoint(起点)和secondPoint(终点)。 - 调用
this.CreateGraphics()获取窗体本身的图形上下文g。 - 调用
this.chart1.CreateGraphics()获取 Chart 控件的图形上下文g2。
完成状态:类内包含可供事件处理器共享的图形对象与状态标识。
public partial class DataForm : Form
{
// 状态控制
public bool begin = false;
public Point firstPoint = new Point(0, 0);
public Point secondPoint = new Point(0, 0);
// 图形上下文
private Graphics g; // 对应窗体
private Graphics g2; // 对应 chart 控件
public DataForm()
{
InitializeComponent();
// 必须在初始化组件后获取 Graphics 对象
g = this.CreateGraphics();
// 确保 chart1 已在设计器中放置
if (this.chart1 != null)
{
g2 = this.chart1.CreateGraphics();
}
}
}
第2步:实现窗体上的实时绘制(MouseDown/Move/Up)
目的:处理用户在窗体非 Chart 区域的点击与拖动,绘制覆盖层矩形。
入口:绑定到窗体的 MouseDown, MouseMove, MouseUp 事件。
动作与逻辑:
data_MouseDown:将begin设为true,记录当前点击位置为firstPoint。data_MouseMove:
- 若begin为假则忽略。
- 使用g.Clear(this.BackColor)清除之前的画面(重绘)。这一步模拟了“刷新”。
- 更新secondPoint为当前鼠标位置。
- 计算矩形的左上角 (minX, minY) 和右下角 (maxX, maxY) 坐标,使用Math.Min/Max兼容任意方向的拖动。
- 调用g.DrawRectangle(new Pen(Color.Red), ...)画出红色矩形。data_MouseUp:将begin设为false,结束绘制循环。
// 窗体事件处理示例
private void DataForm_MouseDown(object sender, MouseEventArgs e)
{
begin = true;
firstPoint = new Point(e.X, e.Y);
}
private void DataForm_MouseMove(object sender, MouseEventArgs e)
{
if (begin)
{
// 1. 清除并重画窗体背景
g.Clear(this.BackColor);
// 2. 更新终点
secondPoint = new Point(e.X, e.Y);
// 3. 规范化坐标
int minX = Math.Min(firstPoint.X, secondPoint.X);
int minY = Math.Min(firstPoint.Y, secondPoint.Y);
int width = Math.Abs(secondPoint.X - firstPoint.X);
int height = Math.Abs(secondPoint.Y - firstPoint.Y);
// 4. 绘制矩形
g.DrawRectangle(new Pen(Color.Red, 2), minX, minY, width, height);
}
}
private void DataForm_MouseUp(object sender, MouseEventArgs e)
{
begin = false;
}
第3步:实现 Chart 控件上的特殊绘制
目的:在图表内部进行视觉标注。**注意**:Chart 控件的渲染机制与普通窗体不同,直接 Clear 会破坏图表数据区的完整性。
入口:绑定到 chart1 控件的事件。
关键差异:
- 不能用
g2.Clear(),否则会擦除整个图表的背景和数据序列。 - 应使用
this.Refresh()强制控件重绘(或使用双缓冲技术以避免闪烁)。
动作与逻辑:
chart1_MouseDown:开启绘制状态,记录起点。chart1_MouseMove:
- 调用this.Refresh()触发重绘以消除前一个帧的痕迹。
- 重新计算坐标,调用g2.DrawRectangle绘制新的矩形。chart1_MouseUp:关闭状态。
// Chart 控件事件处理示例
private void chart1_MouseDown(object sender, MouseEventArgs e)
{
begin = true;
firstPoint = new Point(e.X, e.Y); // 注意:e.X/e.Y 是相对于 ClientArea 的坐标
}
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
if (begin)
{
// 必须使用 Refresh 而不是 g2.Clear,以免破坏图表内部结构
this.Refresh();
secondPoint = new Point(e.X, e.Y);
int minX = Math.Min(firstPoint.X, secondPoint.X);
int minY = Math.Min(firstPoint.Y, secondPoint.Y);
int width = Math.Abs(secondPoint.X - firstPoint.X);
int height = Math.Abs(secondPoint.Y - firstPoint.Y);
g2.DrawRectangle(new Pen(Color.Red, 2), minX, minY, width, height);
}
}
private void chart1_MouseUp(object sender, MouseEventArgs e)
{
begin = false;
}
完成判断
编译运行程序后:
1. 在窗体任意空白处按下鼠标拖动,应能看到红色方框实时跟随鼠标移动且背景正常刷白。
2. 在 Chart 控件区域内按下鼠标拖动,红色方框同样跟随移动,且下方的图表曲线和数据标签不消失、不被擦除。
原文有依据的排错
- 闪烁问题:上述基础实现使用了简单的
Clear/Draw或Refresh,在高 DPI 或复杂图表下可能出现闪烁。进阶方案需启用窗体双缓冲 (DoubleBuffered = true) 或使用Invalidate()替换Refresh。 - 坐标偏移:如果在容器内(Panel等)绘图,
e.X可能需要进行偏移调整,但在纯窗体或 Chart 客户区通常直接使用。
简要总结
利用 C# GDI+ 进行实时绘图的核心在于掌握 Graphics 对象的生命周期管理以及响应鼠标事件的坐标计算逻辑。对于普通控件,简单的清屏重绘即可满足需求;而对于 Chart 等具有内部渲染机制的复合控件,务必避免直接调用 Clear 破坏其子元素,应采用控件级别的刷新策略。以上就是 C# GDI+ 在窗体和 Chart 控件上进行鼠标绘图操作的详细内容,更多关于 C# GDI+ 绘图技巧的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













