在C#的Windows Forms开发中,GDI+是最基础也最常用的二维图形绘制技术。借助GDI+,开发者可以在窗体、Panel或者自定义控件上绘制直线、矩形、椭圆、文本以及图像,实现数据可视化、报表设计和游戏原型等功能。理解GDI+的体系结构,是掌握C#图形编程的第一步。

一、GDI+核心对象与获取Graphics的方式
GDI+的核心类是System.Drawing.Graphics,它相当于一块画布,所有的绘制操作都通过它的方法完成。很多新手容易犯的错误是直接使用new Graphics(),实际上Graphics没有公开构造函数,必须借助已有对象的上下文来创建。
最常用的获取方式有三种。第一是在控件的Paint事件中使用e.Graphics,这是最推荐的做法,因为系统会自动管理绘制时机和裁剪区域。第二是调用控件的CreateGraphics()方法,适合临时绘制,但窗体被遮挡或重绘时会丢失内容。第三是通过Graphics.FromImage()从位图创建,用于离屏绘制或保存图片。下面的代码展示了Paint事件中的基本用法:
using System.Drawing;
using System.Windows.Forms;
public class DrawForm : Form
{
public DrawForm()
{
this.Paint += DrawForm_Paint;
}
private void DrawForm_Paint(object sender, PaintEventArgs e)
{
// 通过事件参数获取Graphics对象
Graphics g = e.Graphics;
// 创建红色画笔
Pen pen = new Pen(Color.Red, 2);
// 绘制一条从(10,10)到(200,100)的直线
g.DrawLine(pen, 10, 10, 200, 100);
pen.Dispose();
}
}
1.1 画笔与画刷的区别
Pen用于绘制线条和轮廓,可以设置颜色、宽度、虚线样式;Brush用于填充封闭区域,例如SolidBrush实心填充、LinearGradientBrush渐变填充。混淆两者是常见误区,Draw方法用Pen,Fill方法用Brush。
在使用完Pen和Brush后应当调用Dispose()释放非托管资源,或者使用using语句块。否则长时间运行可能导致GDI对象泄漏,表现为界面绘制变慢甚至系统报资源不足。
二、常见图形绘制方法实践
Graphics类提供了一组直观的绘制函数,例如DrawRectangle、FillEllipse、DrawString等。坐标系默认以控件左上角为原点,向右为X正方向,向下为Y正方向,这与数学笛卡尔坐标系不同,需要适应。
下面示例在Panel上绘制一个带渐变填充的矩形和一段文字,并演示了如何安全释放资源:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
// 使用using自动释放画刷
using (LinearGradientBrush brush = new LinearGradientBrush(
new Rectangle(0, 0, 150, 80),
Color.LightBlue,
Color.DarkBlue,
LinearGradientMode.Vertical))
{
g.FillRectangle(brush, 10, 10, 150, 80);
}
using (Pen pen = new Pen(Color.Black))
{
g.DrawRectangle(pen, 10, 10, 150, 80);
}
// 绘制文本
using (Font font = new Font("微软雅黑", 12))
{
g.DrawString("GDI+示例", font, Brushes.White, 20, 40);
}
}
2.1 坐标变换与平滑处理
如果希望旋转或缩放图形,可以使用g.TranslateTransform和g.RotateTransform。此外设置g.SmoothingMode = SmoothingMode.AntiAlias能够抗锯齿,让边缘更平滑,但会轻微影响性能。
需要注意变换是累加的,若多次调用变换方法而未重置,图形位置可能偏离预期。可以在绘制前调用g.Save()保存状态,绘制后g.Restore()恢复,避免相互干扰。
三、自定义控件重绘与双缓冲
当我们需要在控件上持续更新图形,比如实时曲线或动画,仅依赖单次Paint不够。应当调用Invalidate()通知系统重绘,并在OnPaint中根据当前数据画图。频繁闪烁是典型问题,解决办法是开启双缓冲。
自定义控件中可以将DoubleBuffered设为true,或者设置SetStyle(ControlStyles.OptimizedDoubleBuffer, true)。以下代码展示一个最简自定义圆形进度控件:
public class CircleProgress : Control
{
public int Value { get; set; } = 0;
public CircleProgress()
{
this.DoubleBuffered = true;
this.Size = new Size(120, 120);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// 画底环
using (Pen back = new Pen(Color.LightGray, 10))
{
g.DrawEllipse(back, 10, 10, 100, 100);
}
// 画进度弧
using (Pen fore = new Pen(Color.Green, 10))
{
int angle = (int)(360.0 * Value / 100);
g.DrawArc(fore, 10, 10, 100, 100, 0, angle);
}
}
}
3.1 重绘时机与性能权衡
调用Invalidate()不会立即重绘,而是将控件加入无效区域,下一次消息循环时触发Paint。若需立刻更新可接着调用Update()或Refresh(),但后者会强制同步重绘,频繁使用易造成界面卡顿。
对于复杂场景,推荐把图形先画到Bitmap上,再在Paint中一次性DrawImage,这样既能利用双缓冲,又方便做图层管理。配合Task或Timer更新数据模型,再通知界面重绘,是较为稳健的架构。
四、常见误区与调试建议
一个广泛存在的误解是认为CreateGraphics得到的对象可以持久保存并随时画。实际上窗口重绘时系统会使用Paint事件里的Graphics覆盖之前内容,因此持久绘图必须放在Paint或缓存位图中。
调试GDI+问题可以借助g.VisibleClipBounds查看裁剪区,或临时输出坐标值。若图形不显示,优先检查控件尺寸是否为0、画笔颜色是否与背景相同、以及是否忘记调用Invalidate。掌握这些细节后,C# GDI+就能稳定支撑各类桌面图形需求。
GDI+CSharp_drawingGraphics修改时间:2026-08-09 20:30:30