ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C# WinForms 用 GDI+ 从 SQL Server 读取数据绘制曲线完整示例

C# WinForms 用 GDI+ 从 SQL Server 读取数据绘制曲线完整示例 简介一套面向C#初学者的曲线绘制与SQL数据库联动示例工程围绕WinForms/GDI绘图与ADO.NET数据交互展开适合需要同时练习图形界面绘图、曲线生成和数据库存取操作的开发者。压缩包共25个文件核心为6个cs源码、sln解决方案与SQL建表脚本另含resx资源、exe可执行程序及settings配置文件整体仅50KB体量精简可直接用Visual Studio打开工程对照学习。项目演示了用Pen、Graphics绘制由坐标点或控制点生成的曲线涵盖贝塞尔曲线等常见绘制方式并通过SqlConnection、SqlCommand将曲线坐标写入数据库或读取表中记录后动态重绘。其中scriptdb.sql提供建表脚本Form1等窗体代码串联起“绘图—存储—查询—重绘”完整流程可理解数据表设计与INSERT/SELECT语句在桌面应用中的实际用法。已有253人学习下载适合课堂练习、课程设计或自学巩固尤其有助于将GDI图形编程与数据库操作结合到同一项目中进行综合实践。1. C# 绘制曲线示例源码含 SQL 数据库从采集点到像素点的完整链路只看标题很多人会误以为这是一份“画曲线的 Demo”。真正拆完之后你会发现它其实是把“曲线绘制 数据库存取”串成一条完整链路的工程样例数据库里存了 X/Y 坐标与时间点程序启动后从 SQL Server 读回这些点再通过 GDI 把它们画到窗体上。如果你做过上位机、做过分组数据可视化应该懂这种场景——画图本身不复杂复杂的是“下班前领导要你把数据库里那批历史数据还原成曲线”你翻遍全网却找不到一个能把读库、建点、绘制串起来讲的例子。这就是这份 Case06_11 值得下载的原因。它适合刚接触 C# 与 SQL 配合开发的入门者也适合给毕设或小工具临时补一个“数据回放”功能的人。2. 拆开 Case06_11 的源码包从解决方案结构到建库顺序动手跑这份源码之前我建议先花十分钟把资源包里的文件结构过一遍。你拿到手的是一个标准 WinForms 解决方案里面既有编译产物也有源码还有一份独立的 SQL 建库脚本。别急着双击 .sln先把每个文件该干什么摸清楚后面调试会少踩很多坑。2.1 文件清单还原一个 WinForms 解决方案的标配布局打开解压目录后你会看到下面这些文件。我以实际工程习惯帮你把它们分成三组分类文件/目录作用解决方案入口Case06_11.slnVisual Studio 解决方案文件双击即可打开整个项目工程配置Case06_11.csproj项目文件记录目标框架、引用程序集、编译配置源码文件Form1.cs、Form1.Designer.cs、Form1.resx主窗体的逻辑代码、设计器代码与资源文件程序入口Program.csMain 方法所在负责启动 Application 消息循环属性配置Properties/Settings.settings、Resources.resx程序设置与资源定义相关 Designer.cs 由 VS 自动生成数据库脚本scriptdb.sql建库建表脚本是整个示例的数据基础编译产物bin/Debug、obj/Debug项目编译输出的程序集和中间文件运行前会自动重建Form1.cs 是核心文件曲线绘制和数据库交互逻辑大概率都集中在里面Form1.Designer.cs 里则是设计器生成的控件布局代码。至于 bin/Debug 里可能已经有编译好的 exe但我不建议你直接跑旧产物——环境不同还是重新编译更稳妥。2.2 环境准备Visual Studio 与 SQL Server 的版本对齐这类示例项目多数运行在 .NET Framework 4.x 上因此你的开发环境需要满足以下条件Visual Studio 2019 或 2022装好“.NET 桌面开发”工作负载SQL Server 2016 或更高版本本地开发用 LocalDB 或 Express 版都行确认 SQL Server 服务已启动且 TCP/IP 协议处于启用状态打开 SQL Server 配置管理器看一下“SQL Server 网络配置”里 TCP/IP 是否已启用。这个点很关键后面避坑部分会详细说。我第一次跑类似项目时没启用 TCP/IP程序一直报连接超时白白排查了一晚上。2.3 首次启动的先后顺序先把数据库建好我建议的顺序是先执行 scriptdb.sql 建库再打开解决方案编译运行。不要一上来就按 F5因为程序启动后大概率会尝试连接数据库库不存在时界面要么空白要么直接抛异常。执行 SQL 脚本最直接的方式是用 sqlcmd 命令行工具sqlcmd -S localhost -E -i scriptdb.sql参数说明-S localhost指定本机 SQL Server 实例-E表示使用 Windows 身份验证-i指定要执行的脚本文件路径。如果你用的是命名实例把 localhost 改成localhost\\SQLEXPRESS这样的形式。脚本执行成功后打开 Case06_11.sln等待 NuGet 还原完成然后按 F5 编译运行。如果一切正常程序会连上数据库并准备读取曲线数据如果窗体上还没看到曲线说明库里还没有数据你需要先通过程序界面“写入”或手动往表里插入几个点。3. 曲线绘制的实现路径用 GDI 把数据点变成图形聊到曲线绘制很多人第一时间想到的是 Chart 控件或者第三方图表库。但这份源码用的是 WinForms 自带的 GDI原因其实很实在轻量、可控、无额外依赖。你用 Chart 控件拖拖拽拽也能出图但一旦要画点、描边、统一坐标轴、重绘缩放GDI 的灵活性反而更高也更能帮你理解图形绘制的底层逻辑。3.1 为什么选 GDI 而不是 Chart 控件在工业数据采集、上位机界面这类场景里实时性与可定制性往往比图表美观度更重要。Chart 控件自带图例、坐标轴、缩放工具功能丰富但性能开销也大而 GDI 的Graphics对象是一条轻量管线你完全可以控制从 Pen 到坐标变换的每一个细节。这正好对应了“源码工程示例”的定位——它教你的是原理而不是控件的点选操作。3.2 从数据到曲线Graphics.DrawCurve 的主干代码看源码之前我先把 GDI 绘曲线的最小可用流程拆成四步创建画布 → 构建点数组 → 创建画笔 → 调用 DrawCurve。下面是一段可直接运行在 Form 里的核心代码它把内存中的ListPointF渲染成一条平滑曲线using System.Drawing; using System.Drawing.Drawing2D; using System.Collections.Generic; // 在窗体 OnPaint 或按钮事件里调用 private void DrawCurveFromPoints(Graphics g, ListPointF pointsList) { if (pointsList null || pointsList.Count 2) return; // 将 ListPointF 转成 GDI 要求的 PointF 数组 PointF[] pts pointsList.ToArray(); // 创建一个 2 像素宽的深蓝色画笔 using (Pen pen new Pen(Color.FromArgb(30, 80, 180), 2.0f)) { // 开启抗锯齿避免曲线边缘出现明显锯齿 g.SmoothingMode SmoothingMode.AntiAlias; // 核心绘制调用根据点集自动生成平滑曲线 g.DrawCurve(pen, pts, 0.5f); } }这段代码的逻辑很直白DrawCurve会根据你传入的PointF数组自动计算三次样条曲线路径第三个参数0.5f是张力系数控制曲线弯曲程度值越大曲线越贴近点之间的折线段值越小越圆滑。建议新手从 0.5 起步再按实际视觉效果微调。需要注意的是DrawCurve在点数量特别大时性能下降明显我一般超过 500 个点就不直接全量绘制了而会先抽稀或者缩放坐标系这个放在后面进阶章节细说。3.3 画曲线最容易忽略的三件事Pen、坐标变换与抗锯齿画好几条曲线的关键不在DrawCurve本身而在你如何管理Pen和Graphics状态Pen一定要用using包裹或手动DisposeGDI 对象是稀缺资源不释放会积累 GDI 泄漏如果数据点的量级是上千而你直接用原始坐标绘制曲线可能直接画出屏幕外。常见的做法是先算出数据集的 Min/Max 范围再做归一化映射到窗体ClientSize上SmoothingMode一定要设置。默认的 None 模式画出来的曲线边缘有明显锯齿视觉上很掉档次设置成AntiAlias之后效果立竿见影坐标归一化这段我一般单独抽一个函数出来这叫是提高复用率的写法private PointF[] NormalizePoints(ListPointF rawPoints, Size canvasSize) { if (rawPoints.Count 0) return new PointF[0]; float minX float.MaxValue, maxX float.MinValue; float minY float.MaxValue, maxY float.MinValue; // 先遍历所有点找到数据范围 foreach (PointF p in rawPoints) { if (p.X minX) minX p.X; if (p.X maxX) maxX p.X; if (p.Y minY) minY p.Y; if (p.Y maxY) maxY p.Y; } float rangeX maxX - minX; float rangeY maxY - minY; if (rangeX 0) rangeX 1f; if (rangeY 0) rangeY 1f; PointF[] result new PointF[rawPoints.Count]; for (int i 0; i rawPoints.Count; i) { float normX (rawPoints[i].X - minX) / rangeX * canvasSize.Width; float normY canvasSize.Height - (rawPoints[i].Y - minY) / rangeY * canvasSize.Height; result[i] new PointF(normX, normY); } return result; }注意normY那行用到了canvasSize.Height - ...这是因为屏幕坐标系是从上往下递增的而数学坐标系是从下往上。如果不做翻转画出来的曲线会是上下镜像的——这个坑我在刚开始画波形时翻车过好几次。4. 把曲线数据交给 SQL Serverscriptdb.sql 的设计与 ADO.NET 调用链绘制曲线只是表象这份源码真正有价值的部分是把数据持久化到了 SQL Server。理解了 scriptdb.sql 和 C# 侧的调用链你就能把这一套移植到自己的数据采集项目里。4.1 scriptdb.sql 在做什么库表结构与字段取舍虽然我手上这份脚本的具体表结构因版本而异但常见的设计思路是建一张曲线数据表字段类似这样CREATE DATABASE CurveDemo; GO USE CurveDemo; GO CREATE TABLE dbo.CurvePoints ( Id INT IDENTITY(1,1) PRIMARY KEY, RecordTime DATETIME NOT NULL DEFAULT GETDATE(), XValue FLOAT NOT NULL, YValue FLOAT NOT NULL ); GO CREATE INDEX IX_CurvePoints_RecordTime ON dbo.CurvePoints(RecordTime); GO这里有几个字段设计上的关键决策值得说。XValue和YValue用FLOAT而不用DECIMAL是因为曲线数据通常是传感器采样值精度与范围优先金融计算才需要定点精度。RecordTime是排序和筛选依据加索引能让按时间段检索曲线点的查询快很多。至于Id自增主键主要是为了支持后续删除、更新或分页。4.2 C# 连接 SQL Server连接串、参数化写入与异常处理有了表之后下一步是从 C# 侧写入数据。最稳妥的方式是用 ADO.NET 的SqlConnectionSqlCommand加上参数化查询防止 SQL 注入。using System.Data.SqlClient; private static void InsertCurvePoint(string connectionString, float x, float y) { const string sql INSERT INTO dbo.CurvePoints (RecordTime, XValue, YValue) VALUES (time, x, y);; using (SqlConnection conn new SqlConnection(connectionString)) { using (SqlCommand cmd new SqlCommand(sql, conn)) { // 参数化把外部值作为参数传入避免拼接 SQL cmd.Parameters.AddWithValue(time, DateTime.Now); cmd.Parameters.AddWithValue(x, x); cmd.Parameters.AddWithValue(y, y); conn.Open(); cmd.ExecuteNonQuery(); } } }连接串长这样本地开发用 Windows 身份验证最省心string connStr Data Sourcelocalhost;Initial CatalogCurveDemo;Integrated SecurityTrue;;AddWithValue虽然写起来方便但遇到NVARCHAR、DATETIME2类型时可能存在类型推断问题。更稳妥的做法是显式指定SqlDbType例如cmd.Parameters.Add(time, SqlDbType.DateTime2).Value DateTime.Now;。这是血泪经验后面避坑章会展开。4.3 从数据库读取曲线数据DataReader 与点集重建写入能跑通后读回数据就是对称操作。建议用SqlDataReader做只进式流式读取不要每次把整张表DataTable载入内存数据量一大就会很卡。private ListPointF LoadCurvePoints(string connectionString, DateTime startTime, DateTime endTime) { ListPointF results new ListPointF(); const string sql SELECT XValue, YValue FROM dbo.CurvePoints WHERE RecordTime start AND RecordTime end ORDER BY RecordTime ASC;; using (SqlConnection conn new SqlConnection(connectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue(start, startTime); cmd.Parameters.AddWithValue(end, endTime); conn.Open(); using (SqlDataReader reader cmd.ExecuteReader()) { while (reader.Read()) { float x (float)reader[XValue]; float y (float)reader[YValue]; results.Add(new PointF(x, y)); } } } return results; }读出来后直接把返回的ListPointF交给上一章的DrawCurveFromPoints方法就完成了“数据库 → 内存 → 窗体”的完整闭环。这也是这个示例项目最值得学习的地方——它不是教你画图而是教你让图“活”在数据上。5. 避坑与排查GDI 与 SQL Server 组合实战的真实翻车记录以下 5 条都是我或身边同事在写 C# 曲线绘制 SQL Server 时真实踩过的坑。每一条都按“现象 → 原因 → 解决”的顺序记录你可以直接对着检查。5.1 连接超时sqlcmd 能连但程序连不上现象sqlcmd 命令行查询正常C# 程序一运行就报“连接超时”或“无法打开登录所请求的数据库”。原因大部分情况下是 SQL Server 的 TCP/IP 协议未启用或连接串里的Data Source写法不对。程序走的 TCP 1433 端口而 sqlcmd 可能走了共享内存协议。解决打开 SQL Server 配置管理器启用 TCP/IP重启 SQL Server 服务。同时把连接串写成Data Sourcelocalhost,1433明确指定端口能少很多歧义。5.2 画出来的曲线像折线张力系数与点密度不对现象数据点传进去后曲线变成生硬的折线完全没有“平滑曲线”的观感。原因常见的有两种一是DrawCurve的张力参数被设成了 0曲线退化为直线连接二是点太稀疏样条曲线没有足够的控制点来形成弧度。解决把张力第三个参数调整到 0.50.7 之间。稀疏点可以考虑先用三次插值补点再做绘制。补点可以参考Math线性插值手段但要注意不要过度插值导致曲线失真实测意义。5.3 中文字段读出乱码编码与参数类型双重问题现象从 SQL Server 读出的数据含中文时显示为“???”或乱码。原因表字段是VARCHAR且数据库排序规则不是中文相关或者 SQL 脚本文件本身保存成了 UTF-8 无 BOM 格式在 SSMS 里执行时被按 ANSI 解析了。解决建表时字符串字段一律用NVARCHAR读取脚本存成带 BOM 的 UTF-8C# 侧连接串里加上Character Setutf8意义不大因为 SqlClient 是自动处理 Unicode 的乱码源头基本在表类型不在连接串。5.4 曲线边缘发虚高 DPI 缩放下 GDI 的经典问题现象程序在 100% 缩放的电脑上正常换到 125% 或 150% 缩放的笔记本上曲线明显模糊甚至错位。原因WinForms 默认不支持 Per-Monitor DPI 感知系统对窗体做了位图拉伸GDI 绘制结果被整体放大导致发虚。解决在 app.manifest 里启用 DPI 感知并在Program.cs入口调用Application.SetHighDpiMode(HighDpiMode.SystemAware)让窗体按物理像素绘制。application xmlnsurn:schemas-microsoft-com:asm.v3 windowsSettings dpiAware xmlnshttp://schemas.microsoft.com/SMI/2005/WindowsSettingstrue/dpiAware /windowsSettings /application5.5 反复刷新后界面卡死Graphics 未释放与全量重绘现象多次点击“刷新曲线”按钮后程序越来越卡CPU 飙升。原因每次刷新都新建Graphics对象但没有释放或者刷新时把整个窗体Invalidate()导致频繁全量重绘。解决所有Pen、Graphics对象用using包裹或手动Dispose。刷新时只对曲线所在区域做局部失效例如Invalidate(new Rectangle(10, 10, 500, 300))减少重绘面积。6. 进阶玩法多曲线回放与数据缓存策略跑通基础流程后可以试着把这个示例扩展成“历史数据回放”工具。最常见的场景是界面上有开始时间、结束时间两个 DateTimePicker点查询后一次画两条曲线一条是某个传感器的温度数据另一条是它的变化率方便对比找异常。多曲线的实现要点是把不同数据源的点数组存到内存缓存里避免每次查询都访问数据库private Dictionarystring, ListPointF _curveCache new Dictionarystring, ListPointF(); public void LoadCurvesWithCache(string key, DateTime start, DateTime end, string connStr) { if (!_curveCache.ContainsKey(key)) { _curveCache[key] LoadCurvePoints(connStr, start, end); } ListPointF points _curveCache[key]; // 再传入绘制函数 }缓存时需要注意数据源的新增点是缓存的天然敌人。如果设备持续写入数据库你需要给查询按钮加一个“强制刷新”选项或在缓存写入时记录最后更新时间超时后自动失效。另外还有一个实用习惯绘制多条曲线时给每条Pen分配独立颜色和线宽并在图例区域标明对应关系。颜色最好用色板里区分度高的几组值避开红绿这样对色弱用户不友好的组合。从个人习惯来说我现在每拿到一个陌生项目第一件事一定是先找 .sln 入口第二步就执行它的 SQL 脚本绝不能跳过建库这一步直接按 F5。这样一套顺序走下来大多数“跑不起来”的问题都能在 10 分钟内定位。希望这份 Case06_11 的拆解能帮到你。本文还有配套的精品资源点击获取
返回列表