简介这份C#绘制曲线示例源码面向具备一定C#基础、希望掌握数据可视化与图形界面开发的开发者重点解决如何将SQL数据库中的数据读取后动态绘制成曲线的问题。资源包共25个文件约50KB以cs源码、csproj项目文件、sln解决方案、sql脚本及resx资源文件为主另含exe、dll、pdb等编译产物结构完整可直接在Visual Studio中打开运行。示例通过ADO.NET连接数据库、执行查询并解析结果再借助GDI或WPF的Graphics类完成曲线绘制涵盖连接、查询、解析、绘制到显示的完整链路并留有异步加载、缓存图形、动态更新与鼠标交互等优化扩展空间。目前已有748人学习下载适合想打通数据库与图形编程、提升数据可视化实战能力的读者参考借鉴。1. 从一份 WinForm 曲线源码说起它到底能解决什么问题很多做 C# 上位机或者数据采集的同行手里都攒着一堆“能跑但不敢改”的祖传代码尤其是曲线绘制这块。这份Case06_11源码包就是一个典型的、能直接跑起来的 WinForm 曲线绘制示例而且它把 SQL 数据库也串了进来。你拿到手解压后会看到Form1.cs、Program.cs、scriptdb.sql以及一个完整的.sln解决方案文件。它解决的核心问题很具体数据从 SQL 数据库里查出来经过 C# 处理最后用 GDI 画成曲线显示在窗体上。适合谁呢刚接触 C# 图形编程、需要快速交一个数据可视化 Demo 的新手或者想看看老式 WinForm 项目怎么组织数据库连接与绘图逻辑的熟手。它不涉及复杂的 WPF 数据绑定就是最朴素的Graphics.DrawCurve和SqlDataReader但恰恰是这种朴素让你能看清每一步的“黑匣子”里到底装了什么。2. 拆解 Case06_11GDI 绘图与 SQL 数据流的底层逻辑2.1 为什么是 GDI 而不是 WPF 或第三方图表库打开Form1.Designer.cs你会看到窗体上大概率放了一个PictureBox或者直接重写了OnPaint。这份源码选择 GDI 是很有代表性的。在 WinForm 里GDI 是System.Drawing命名空间下的一套绘图 API它直接操作窗口的图形上下文。相比于 WPF 的Path和PolylineGDI 的DrawCurve方法对新手更友好——你只需要给它一个Pen和一个Point[]数组它就能帮你把点连成平滑曲线。而第三方库如ZedGraph或OxyPlot虽然功能强但引入依赖、学习曲线陡峭对于只想理解“点怎么变成线”的人来说反而是干扰。这份源码的价值就在于它把最核心的Graphics.DrawCurve(Pen, Point[])调用暴露在你面前没有封装成黑盒。你改一个坐标点曲线就跟着动这种即时反馈对建立图形编程的直觉非常重要。2.2 数据库脚本 scriptdb.sql 里藏着什么scriptdb.sql这个文件是理解整个数据流的钥匙。用文本编辑器打开它通常你会看到CREATE TABLE语句可能还有几条INSERT测试数据。常见做法是建一张类似CurveData的表字段包括Id、XValue、YValue、RecordTime。为什么要有RecordTime因为实际采集场景里X 轴往往是时间序列而不是简单的 1、2、3。这份源码的 SQL 脚本大概率定义了浮点型的 X 和 Y 列因为曲线绘制需要连续的数值。如果你拿到脚本后直接执行记得先确认数据库名称和连接字符串是否匹配。我一般会先在 SSMS 里跑一遍脚本看看表结构再回到 Visual Studio 里改App.config或Form1.cs里的连接字符串。这一步不做后面程序一跑就报“无法打开登录所请求的数据库”这是血泪经验。2.3 从 SqlDataReader 到 Point 数组的转换逻辑Form1.cs里最值得细看的是数据读取和转换部分。它不会直接把SqlDataReader丢给绘图函数中间必然有一个ListPointF或者Point[]的构建过程。下面这段代码是我根据这类项目的常见写法还原的核心逻辑你可以对照源码里的实际变量名去理解// 假设连接字符串已从配置读取 using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql SELECT XValue, YValue FROM CurveData ORDER BY XValue; using (SqlCommand cmd new SqlCommand(sql, conn)) using (SqlDataReader reader cmd.ExecuteReader()) { ListPointF points new ListPointF(); while (reader.Read()) { // 注意GetFloat 要求数据库列类型是 real 或 float // 如果是 decimal需要先 Convert.ToSingle float x reader.GetFloat(0); float y reader.GetFloat(1); points.Add(new PointF(x, y)); } // 将 List 转为数组因为 DrawCurve 接受 PointF[] PointF[] curvePoints points.ToArray(); // 在 PictureBox 的 Paint 事件或自定义控件中绘制 // e.Graphics.DrawCurve(myPen, curvePoints); } }这段代码的逻辑说明SqlDataReader是只进只读的游标适合一次性把数据拉出来。GetFloat的索引 0 和 1 对应SELECT语句里的列顺序。参数上ORDER BY XValue很关键如果数据库里的点不是按 X 轴排序的DrawCurve画出来的线会来回折返看起来像一团乱麻。另外PointF用的是浮点坐标比Point的整型精度高适合科学数据。如果你在源码里看到的是int转换那说明数据源可能是整数类型但曲线平滑度会受影响。2.4 解决方案文件与项目结构速览Case06_11.sln是入口双击它Visual Studio 会加载整个项目。Case06_11.csproj里记录了目标框架版本常见的是 .NET Framework 4.0 或 4.5。Properties文件夹下的AssemblyInfo.cs管版本号Resources.resx管窗体图标等资源。bin和obj是编译输出目录不用管。重点看Form1.resx它和Form1.cs是配对的里面存了窗体控件的布局信息。如果你改了窗体设计器里的控件位置这个文件会自动更新。整个项目结构非常扁平没有分层架构所有逻辑都堆在Form1.cs里。对于学习来说这是好事你不需要在多个文件间跳转对于实际项目这就是需要重构的信号。3. 动手复现从附加数据库到曲线显示的全流程3.1 环境准备与数据库附加的两种方式在跑代码之前你得先把 SQL 数据库准备好。scriptdb.sql给了你两种选择一是直接执行脚本创建新库二是附加一个现成的.mdf文件如果源码包里带了的话。我一般推荐执行脚本因为这样你能看到建表语句心里有底。打开 SSMS连接到本地localhost或.\SQLEXPRESS新建查询把scriptdb.sql的内容粘进去执行。如果脚本里没有CREATE DATABASE只有CREATE TABLE那你得先手动建一个空库再在目标库里执行建表语句。执行完后SELECT * FROM CurveData看看有没有测试数据。如果没有自己插几条比如INSERT INTO CurveData VALUES (1.0, 2.5), (2.0, 3.1), (3.0, 2.8)。这一步是后面曲线能不能画出来的前提。3.2 修改连接字符串并验证数据库连通性回到 Visual Studio打开Form1.cs找到SqlConnection的初始化位置。连接字符串通常长这样Server.;DatabaseCurveDB;Integrated SecurityTrue;。如果你的 SQL 实例名不是默认的把.换成.\SQLEXPRESS或(localdb)\MSSQLLocalDB。Integrated SecurityTrue表示用 Windows 身份验证省去账号密码。改完后不要急着跑整个程序先写个简单的测试在Main方法里或者一个按钮事件里只做conn.Open()然后MessageBox.Show(OK)。如果弹窗成功说明连接没问题如果报错看异常信息里的关键词比如“网络相关或特定于实例的错误”通常是实例名不对“登录失败”是权限问题。这个习惯能帮你把数据库问题和绘图问题分开排查避免眉毛胡子一把抓。3.3 在 PictureBox 上绘制曲线的完整代码假设你的窗体上有一个PictureBox叫pictureBox1下面这段代码展示了从查询到绘制的完整链路。你可以直接抄进Form1.cs的某个按钮点击事件里private void btnDraw_Click(object sender, EventArgs e) { // 1. 准备数据容器 ListPointF points new ListPointF(); string connStr Server.;DatabaseCurveDB;Integrated SecurityTrue;; // 2. 读取数据 using (SqlConnection conn new SqlConnection(connStr)) { conn.Open(); string sql SELECT XValue, YValue FROM CurveData ORDER BY XValue; using (SqlCommand cmd new SqlCommand(sql, conn)) using (SqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { points.Add(new PointF(reader.GetFloat(0), reader.GetFloat(1))); } } } // 3. 数据校验少于两个点画不出曲线 if (points.Count 2) { MessageBox.Show(数据点不足至少需要两个点才能绘制曲线。); return; } // 4. 创建位图并绘制 Bitmap bmp new Bitmap(pictureBox1.Width, pictureBox1.Height); using (Graphics g Graphics.FromImage(bmp)) { g.SmoothingMode System.Drawing.Drawing2D.SmoothingMode.AntiAlias; // 抗锯齿 g.Clear(Color.White); // 背景色 // 坐标变换将数据点映射到图片像素坐标 // 这里简单演示实际项目需要根据数据范围做缩放 float margin 20; float scaleX (bmp.Width - 2 * margin) / (points.Max(p p.X) - points.Min(p p.X)); float scaleY (bmp.Height - 2 * margin) / (points.Max(p p.Y) - points.Min(p p.Y)); PointF[] screenPoints points.Select(p new PointF( margin (p.X - points.Min(pt pt.X)) * scaleX, bmp.Height - margin - (p.Y - points.Min(pt pt.Y)) * scaleY // Y轴翻转 )).ToArray(); using (Pen pen new Pen(Color.Blue, 2)) { g.DrawCurve(pen, screenPoints); } } // 5. 显示到 PictureBox pictureBox1.Image bmp; }逻辑说明这段代码做了几件关键事。第一用ListPointF暂存数据方便后续用 LINQ 求最大最小值。第二Bitmap作为离屏画布避免直接在窗体上画导致刷新闪烁。第三坐标变换是核心——数据库里的 X、Y 可能是任意浮点数而PictureBox的像素坐标是有限的整数范围必须做线性映射。scaleX和scaleY分别计算 X 和 Y 方向的缩放比例margin留出边距防止曲线贴边。第四Y 轴翻转是因为屏幕坐标原点在左上角而数学坐标系原点在左下角。参数上Pen的宽度设为 2 让曲线更明显SmoothingMode.AntiAlias开启抗锯齿让线条更平滑。如果你发现曲线画出来是扁的检查scaleX和scaleY是否因为数据范围差异过大导致比例失调。3.4 参数调整颜色、线宽与坐标轴缩放源码里默认的曲线颜色和线宽可能不符合你的审美或项目要求。改颜色很简单new Pen(Color.Blue, 2)里的Color.Blue换成Color.Red或Color.FromArgb(255, 0, 128)。线宽改第二个参数。但坐标轴缩放是个容易翻车的地方。上面的代码用的是“自动适应数据范围”这会导致每次数据更新曲线都跳来跳去。实际项目中我一般会固定坐标轴范围比如 X 轴固定 0 到 100Y 轴固定 0 到 1000这样曲线变化才能直观对比。固定范围时scaleX和scaleY的分母就变成固定值而不是points.Max - points.Min。另外如果数据点非常密集DrawCurve的默认张力tension是 0.5画出来的曲线可能过冲。你可以用g.DrawCurve(pen, screenPoints, 0.0f)把张力设为 0让曲线变成折线更贴近原始数据。4. 避坑与排查数据库连接和绘图常见的五个翻车点4.1 现象程序启动报“System.Data.SqlClient.SqlException: 无法打开登录所请求的数据库”原因连接字符串里的Database名称和实际附加或创建的数据库名不一致。比如脚本里建的是CurveDB你连接字符串写的是CurveData。或者数据库实例名不对你用了Server.但实际是.\SQLEXPRESS。 解决打开 SSMS确认数据库的确切名称和实例名。在连接字符串里逐字核对。一个快速验证方法是在 SSMS 里新建查询执行SELECT DB_NAME()看当前库名是什么。4.2 现象曲线画出来是一条直线或者几个孤立的点原因DrawCurve要求至少两个点且点必须按 X 轴排序。如果 SQL 查询没有ORDER BY或者数据本身 X 值重复、乱序曲线就会异常。另外如果所有 Y 值相同曲线就是一条水平线这是正常的但容易被误认为没画出来。 解决在 SQL 语句里强制加ORDER BY XValue。在 C# 里加一个判断如果points.Count 2就提示用户。如果 Y 值范围极小检查scaleY是否因为分母接近零而爆炸导致所有点挤在一起。4.3 现象窗体拖动或最小化后曲线消失或闪烁原因直接在窗体的Paint事件里用CreateGraphics()画图没有把图形保存到Bitmap或PictureBox.Image里。窗口重绘时之前画的临时图形就丢了。 解决采用双缓冲或离屏位图。上面的示例代码用的是BitmapPictureBox.Image这样窗口重绘时PictureBox会自动重绘它的Image曲线不会丢。如果你坚持在OnPaint里画记得把PointF[]存为成员变量并在OnPaint里重新绘制。4.4 现象读取数据库时提示“指定的转换无效”原因reader.GetFloat(0)要求数据库列类型是real或float。如果scriptdb.sql里定义的是decimal或numericGetFloat会抛异常。 解决要么改数据库列类型为float要么在 C# 里用Convert.ToSingle(reader.GetDecimal(0))或(float)reader.GetDouble(0)。我一般会在建表时就统一用float省去转换的麻烦。4.5 现象曲线绘制速度慢数据量大时界面卡死原因在 UI 线程里同步执行SqlDataReader读取和DrawCurve绘制。如果数据有几十万行读取和绘图都会阻塞消息循环。 解决用async/await把数据库查询放到后台线程读取完后再回到 UI 线程更新PictureBox.Image。另外对于海量数据点不要全部画出来先做降采样比如每 10 个点取一个或者用Graphics.DrawLines代替DrawCurve因为DrawCurve的平滑计算更耗时。5. 进阶技巧让曲线动起来与数据点交互5.1 用 Timer 实现动态刷新曲线静态曲线看久了没意思实际监控场景需要曲线随时间滚动。在 WinForm 里拖一个Timer控件设置Interval 10001 秒在Tick事件里重新查询最新数据并刷新PictureBox.Image。注意每次刷新都要重新创建Bitmap否则会内存泄漏。下面是一个简化的刷新逻辑private void timer1_Tick(object sender, EventArgs e) { // 只查询最近 60 秒的数据避免全表扫描 string sql SELECT XValue, YValue FROM CurveData WHERE RecordTime DATEADD(second, -60, GETDATE()) ORDER BY XValue; // ... 读取数据到 points ... // 复用上一章的绘制逻辑更新 pictureBox1.Image // 注意旧的 Bitmap 要 Dispose否则内存暴涨 if (pictureBox1.Image ! null) pictureBox1.Image.Dispose(); pictureBox1.Image new Bitmap(...); // 重新绘制 }参数说明DATEADD(second, -60, GETDATE())是 SQL Server 的日期函数表示当前时间往前推 60 秒。如果你的数据库是 MySQL换成DATE_SUB(NOW(), INTERVAL 60 SECOND)。Timer的Interval不要设太小低于 200 毫秒会导致 UI 频繁重绘反而卡顿。5.2 鼠标悬停显示数据点数值这个功能需要给PictureBox绑定MouseMove事件。思路是把屏幕坐标反算回数据坐标然后找最近的数据点用ToolTip显示。反算公式是坐标变换的逆运算dataX (mouseX - margin) / scaleX minX。找到最近点后toolTip1.Show($X{point.X}, Y{point.Y}, pictureBox1, mouseX, mouseY)。注意每次MouseMove都触发查询会卡所以数据点数组要缓存在内存里不要每次都查数据库。5.3 从 WinForm 到跨平台这份源码的边界在哪这份源码基于 .NET Framework 和 WinForm只能在 Windows 上跑。如果你想迁移到 .NET Core 或 .NET 5 的跨平台环境System.Drawing.Common在 Linux 和 macOS 上有限制Graphics.DrawCurve可能无法正常工作。常见做法是换成SkiaSharp或OxyPlot这类跨平台绘图库。但如果你只是想在 Windows 上快速验证一个数据可视化想法这份Case06_11的代码量小、依赖少改起来比引入新库快得多。我自己的习惯是每次拿到这种老源码先跑通再把它当成一个“参考实现”把里面的SqlConnection换成Dapper或Entity Framework把DrawCurve换成OxyPlot的LineSeries逐步现代化。从那以后我每次接手祖传 WinForm 项目都强制先画一遍数据流图确认数据库、内存、绘图三层边界再动手改。希望帮到你。本文还有配套的精品资源点击获取