news 2026/9/7 7:28:22

VC++ MFC中使用MSChart实现柱状图、折线图、饼图完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VC++ MFC中使用MSChart实现柱状图、折线图、饼图完整指南

简介:一份基于VC++与MSChart ActiveX控件的图表绘制源码实例,主要面向MFC程序开发者,尤其适合在报表打印、数据分析与统计展示模块中需要快速输出柱状图、折线图、饼图的场景。项目演示了MSChart控件在MFC框架下的完整接入方法,包括控件初始化、数据绑定、坐标轴设置与图表刷新,并针对常见折线图绘制给出了可运行的参考实现,柱状图与饼图的切换逻辑也一目了然。值得一提,VB中的MSChart示例较多,VC领域的同类资源却相对稀缺,这份实例能帮助开发者绕过控件封装中的典型问题,直接复用核心绘图代码,缩短开发周期。压缩包共113个文件,主体为52个h头文件和51个cpp源文件,涵盖演示工程入口、绘图封装、数据网格等模块,代码文件分工较细,便于按模块定位阅读;另附bmp、ico图标和资源脚本,同时包含dsw、dsp、clw等工程文件,构成一个可直接编译学习的完整VC工程;压缩包为rar格式,整体仅82KB,结构紧凑,便于直接下载对照学习。已有554人学习下载,适合具备一定C++基础、希望快速上手MSChart的MFC开发者。 最近在维护一个老项目时,客户提了个需求:把数据库里的统计数据在界面上用图形展示,柱状图、折线图(项目里习惯叫拆线图)、饼图三种形态能切换。打开工程一看,里面引用的图表控件是老熟人——MSChart,也就是Microsoft Chart Control 6.0 (SP6)。说实话,刚接手时我也犯嘀咕,这上古控件还能不能在新版VS里正常用?但实际做完三个图表后,我反而觉得在VC++的MFC环境里,MSChart依然是做数据可视化最省事的方案:不用引第三方库、不用从头实现绘制逻辑、拖一个控件写几十行代码就能出图。这篇就按我真实的实现顺序,把MSChart环境准备、核心数据模型、三类图表代码、踩过的坑和后续扩展完整过一遍,给正在用VC++做界面统计功能的朋友一份能直接抄作业的实录。

1. 环境准备:控件注册与工程配置

1.1 MSChart控件从哪来

MSChart全名Microsoft Chart Control 6.0 (SP6),是微软随Visual Studio 6.0发布的ActiveX图表组件。它虽然年代久远,但因为接口稳定、部署简单,在MFC项目里一直有存量用户。在VS2017、VS2019、VS2022里,这个控件依然能正常使用,前提是你得先在工具箱里把它请出来。

先确认开发环境。新建工程时选择“MFC应用”,应用程序类型选“基于对话框”,这样最快——热词里有人问VS2017怎么用VC++建立Windows窗体程序,实际就是走这个模板。工程创建完成后,在工具箱空白处点右键,选择“选择项”,在弹出的对话框里切到“COM组件”页,勾选“Microsoft Chart Control 6.0 (SP6)”,确定后工具箱里就会出现一个图表图标,拖到对话框上即可。如果你用的是VS2022,操作路径完全一致,不存在兼容性限制。

1.2 找不到控件时的手动注册与x86限制

如果你在COM组件列表里没找到Microsoft Chart Control,多半是MSChart.ocx没有注册。这时需要管理员权限手动注册。64位系统下注意一个细节:MSChart.ocx是32位控件,注册时需要用32位的regsvr32,也就是C:\Windows\SysWOW64\regsvr32.exe,注册完成后组件列表里才能正常显示。很多人第一次注册失败,就是因为默认调用了64位版本的那个regsvr32,注册完系统提示成功,但VS里依然看不到控件。

还有一个影响中途编译和后期发布的关键点:MSChart.ocx没有64位版本。也就是说,你的MFC工程编译目标必须是x86(Win32),不能是x64。否则编译能通过,运行到创建控件时直接报“未注册类别”或崩溃。我见过一个同事在这上面耗了大半天,最后发现就是平台选错。新建工程时把平台选成x86,后面就不要随意切。

另外,MSChart依赖VC++运行库。程序发到其他机器时,目标机器需要安装对应版本的Visual C++ Redistributable,否则程序会提示缺少dll或无法初始化。我实测过,安装VC++ 2015-2022 x86运行库能覆盖大多数MSChart项目的依赖,如果目标机器还有旧项目混用,直接把2015到2022的x86、x64都装一遍更省心。网上流传的“VC++ runtime repair tool”本质上就是批量把系统里残缺的运行库重新补全,遇到客户机器缺库严重时,用这类工具排查很快。

2. 初始化与数据模型:列是序列,行是分类

2.1 控件变量的生成方式

拖入控件后,在控件上右键添加变量,VC++会通过ClassWizard自动生成CMSChart类(mschart.h/mschart.cpp),并在对话框类中绑定一个成员变量。常见的绑定代码在DoDataExchange里:

void CChartDemoDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_MSCHART1, m_Chart); }

如果你不想用对话框模板,也可以直接调用Create动态创建,但对话框拖控件是最省事的路径,初始化也直观。有一点提醒:CMSChart类是从CWnd派生的,它封装了大量COM接口,生成后不要手动去改mschart.h里的定义,否则容易破坏整个类的导出结构。

2.2 行列模型决定了你怎么写数据

用MSChart之前,先把它内部的数据表结构搞清楚,否则代码会绕很多弯路。MSChart的数据本质是一个二维表:Row(行)是分类,Column(列)是序列。

这句话值得仔细琢磨。拿最典型的柱状图举例,假设要展示四个季度的销售额,季度就是分类,所以设置4行;如果还想同时展示去年和今年两组数据做对比,那么每组数据是一个序列,一共2列。数据表长这样:

行(分类)列1(去年)列2(今年)
第一季度100120
第二季度88105
第三季度156170
第四季度96140

这个二维关系直接决定了代码怎么写:先通过SetColumnCount设置列数,用SetRowCount设置行数,再用SetColumnLabel给每一列起名字(也就是图例显示的文字),用SetRowLabel给每一行起名字(也就是X轴显示的分类标签),最后用SetData按坐标填数。这里的坐标关系就是:SetData(行号, 列号, 数值),行号和列号都从1开始。如果只做单序列图表,比如只有一个“销售额”,那就设置1列即可,这种设计在饼图里会特别明显——饼图本质上就是一个序列的多分类占比。

2.3 初始化代码的固定顺序

MSChart初始化时有一个非常容易被忽略的约束:必须先设置行列数,再设置标签和数据。因为每次改变RowCount或ColumnCount,控件内部的数据表都会被重建,之前写入的数据和标签全部清空。我把这个顺序固化成一套模板:

  • 第一步:SetChartType,指定图表类型
  • 第二步:SetColumnCountSetRowCount,定义数据表尺寸
  • 第三步:SetColumnLabelSetRowLabel,设置图例和分类名
  • 第四步:SetData,逐格填充数据

按这个顺序来,基本不会遇到“数据怎么都不显示”的问题。后面踩坑实录里我会再展开讲,如果你发现图表一片空白,先检查是不是把SetData写在了SetRowCount前面。

3. 三种图表的接口调用与完整代码

3.1 柱状图:分类对比的标准写法

柱状图适合做分类对比,比如各部门销售额、各季度出货量。在MSChart里设置图表类型为2D柱状图,使用常量值1。

void CChartDemoDlg::ShowBarChart() { m_Chart.SetChartType(1); // chBar2d m_Chart.SetColumnCount(1); m_Chart.SetRowCount(4); m_Chart.SetColumnLabel(1, _T("销售额")); m_Chart.SetRowLabel(1, _T("第一季度")); m_Chart.SetRowLabel(2, _T("第二季度")); m_Chart.SetRowLabel(3, _T("第三季度")); m_Chart.SetRowLabel(4, _T("第四季度")); m_Chart.SetData(COleVariant((short)1), COleVariant((short)1), COleVariant(120.5)); m_Chart.SetData(COleVariant((short)2), COleVariant((short)1), COleVariant(88.3)); m_Chart.SetData(COleVariant((short)3), COleVariant((short)1), COleVariant(156.2)); m_Chart.SetData(COleVariant((short)4), COleVariant((short)1), COleVariant(96.0)); m_Chart.SetTitleText(_T("2024年度销售额")); }

这里有个重要约定:SetData前两个参数必须用COleVariant((short)行号)的方式强转。直接用COleVariant(1)传int类型,MSChart的COM接口在解析VARIANT时可能报类型不匹配或干脆不写入数据,这种问题不报错、只能靠肉眼在界面上发现,非常坑。如果你还想做多系列对比,把SetColumnCount改成2,再增加一个SetColumnLabel(2, _T("去年")),然后按数据表里的二维坐标填充第二列的数据即可,多系列的图例文字会由MSChart自动生成。

3.2 折线图:趋势展示与多序列叠加

折线图适合连续时间序列的趋势对比。设置图表类型为2D折线图,常量值是3。下面这段代码用12个月的数据,同时展示去年和今年两组趋势:

void CChartDemoDlg::ShowLineChart() { m_Chart.SetChartType(3); // ch2dLine m_Chart.SetColumnCount(2); m_Chart.SetRowCount(12); m_Chart.SetColumnLabel(1, _T("去年")); m_Chart.SetColumnLabel(2, _T("今年")); CString monthName; for (int i = 1; i <= 12; i++) { monthName.Format(_T("%d月"), i); m_Chart.SetRowLabel(i, monthName); } double lastYear[12] = {10, 12, 15, 18, 16, 20, 22, 25, 24, 28, 30, 27}; double thisYear[12] = {11, 14, 13, 19, 21, 23, 26, 29, 27, 33, 35, 31}; for (int i = 1; i <= 12; i++) { m_Chart.SetData(COleVariant((short)i), COleVariant((short)1), COleVariant(lastYear[i-1])); m_Chart.SetData(COleVariant((short)i), COleVariant((short)2), COleVariant(thisYear[i-1])); } m_Chart.SetTitleText(_T("近两年月度趋势对比")); }

折线图和柱状图的代码结构几乎一模一样,只是SetChartType不同。这也侧面说明MSChart的数据模型是统一的:不管最终显示成什么形状,底层都是那张二维表。实际项目里我会把上面的循环逻辑封装成一个函数,传入两个double数组,这样换数据源的时候不用动图表代码。顺带说一句,很多人初次上手时担心折线图的折点太多会不会卡,实测几百个点内MSChart绘制都很流畅,没必要过度优化。

3.3 饼图:占比展示的隐藏约束

饼图适合展示占比结构,比如市场份额、预算分配。设置类型为2D饼图,常量值是14。饼图只需要一列数据,每一行代表一个扇区,MSChart会自动把数值折算成百分比绘制,不用自己计算比例,这个自动折算逻辑省了不少事。

void CChartDemoDlg::ShowPieChart() { m_Chart.SetChartType(14); // ch2dPie m_Chart.SetColumnCount(1); m_Chart.SetRowCount(5); m_Chart.SetColumnLabel(1, _T("市场份额")); m_Chart.SetRowLabel(1, _T("产品A")); m_Chart.SetRowLabel(2, _T("产品B")); m_Chart.SetRowLabel(3, _T("产品C")); m_Chart.SetRowLabel(4, _T("产品D")); m_Chart.SetRowLabel(5, _T("产品E")); m_Chart.SetData(COleVariant((short)1), COleVariant((short)1), COleVariant(30.5)); m_Chart.SetData(COleVariant((short)2), COleVariant((short)1), COleVariant(45.2)); m_Chart.SetData(COleVariant((short)3), COleVariant((short)1), COleVariant(12.0)); m_Chart.SetData(COleVariant((short)4), COleVariant((short)1), COleVariant(8.3)); m_Chart.SetData(COleVariant((short)5), COleVariant((short)1), COleVariant(4.0)); m_Chart.SetTitleText(_T("产品线市场份额分布")); }

饼图有两个容易踩的细节。第一,ColumnCount必须保持为1,如果你手滑设置成2列,MSChart虽然不会报错,但绘制出来的饼图数据归属会混乱。第二,饼图的RowLabel显示位置可能比较靠近圆心或者被截断,不同版本表现不一样,实在显示不全时,可以通过设置字体大小或者调整控件尺寸来缓解。想做3D饼图效果的话,把类型改成常量值15即可,但3D模式下标签的排布会更紧凑,字体设置就更重要了。

4. 踩坑实录:装上之后最容易翻车的四个场景

4.1 图表空白,什么图形都没有

这是我接手项目时遇到的最典型问题。现象:界面上MSChart控件区域一片空白,没有坐标轴、没有柱子,但程序不报错。排查链路是这样的:先把代码整体过一遍,看初始化顺序。MSChart在SetRowCount或SetColumnCount时,会重建内部数据表,意味着之前的SetData和标签设置全会清空。如果你把SetData写在SetRowCount前面,数据就被清掉了。正确顺序必须是:设置图表类型、设置行列数、设置标签、填充数据。

再检查一个隐蔽点:RowCount或ColumnCount是不是设置成了0。比如饼图代码,有人会觉得“饼图没有分类轴”,于是省略了SetRowCount,结果就是图上空白。MSChart内部对行列数量有严格要求,行数和列数都必须至少为1。还有一版我调试时遇到的情况是,把SetData的数值全部传成了0,MSChart绘制时不认为这是有效数据,表现也是空白。给饼图填充数据前可以先手动打印一下数组内容,确认数据源不是全0。

4.2 SetData的数据类型不匹配

SetData的完整签名要求前两个参数是VARIANT类型,而且内部是short(VT_I2),第三个参数是数值类型,通常用double(VT_R8)。我在代码里写的是COleVariant((short)1),有人会写成COleVariant(1),这在编译期完全没问题,但运行期MSChart解析VARIANT时发现类型不符,数据写入失败。最迷惑的是它不弹错误框,只有图形不更新,调试起来特别烦。

我的习惯是封装一个辅助函数,把行号、列号、数值统一包装好再调用:

void CChartDemoDlg::SetChartData(int row, int col, double value) { m_Chart.SetData( COleVariant((short)row), COleVariant((short)col), COleVariant(value)); }

这样所有调用点只需要关注逻辑参数,不需要每处都注意类型强转。如果你的工程使用了ATL的CComVariant,一样要保证是VT_I2和VT_R8类型,原理相同。

4.3 中文标签显示成乱码或问号

中文乱码的问题大多数和字符集设置有关。新建MFC工程时推荐使用Unicode字符集,传字符串时用_T()或者L""前缀,SetRowLabel和SetColumnLabel都按这个来,一般不会乱码。如果还是无法显示,可以尝试给MSChart的字体指定中文字体。CMSChart类封装的接口里有GetFont方法:

m_Chart.GetFont().SetName(_T("宋体")); m_Chart.GetFont().SetSize(9);

这个设置要在填充数据之前做,否则绘制完成后字体改动不会立即刷新。另外,控件尺寸太小也会让中文标签被截断,这种不是乱码而是“显示不全”,建议把控件拉大一些,或者减少分类行数,别指望MSChart自动换行。

4.4 发布到别的机器上无法启动

程序在自己电脑上一切正常,打包发给客户后,客户一打开就弹“未注册类别”或者干脆启动崩溃。这个问题我排查过好几次,根子在于MSChart控件和VC++运行库没有随程序分发。MSChart作为ActiveX控件,目标机器上必须能创建这个COM组件。最稳妥的方式是在安装包里附带MSChart.ocx,安装时执行regsvr32注册,注意64位系统下用C:\Windows\SysWOW64\regsvr32.exe这个32位版本注册。

如果注册成功还是崩,下一步检查运行库:MSChart依赖MSVC运行库,至少要安装VC++ 2015-2022 x86版。很多“VC++ runtime repair tool”类工具就是用来批量修复这类运行库缺失问题的,客户机器上如果缺得厉害,直接用工具扫描修复一次往往就解决了。部署这块最容易忽略的是把运行库做成静默安装,包装到安装包里一次搞定,别指望客户手动装。

5. 从Demo到可用:动态刷新与事件交互的进阶思路

5.1 定时刷新图表数据

实际项目中,图表数据很少是写死在代码里的,更多是从数据库或串口设备实时读取。我常用的做法是在对话框OnInitDialog里启动一个定时器,在OnTimer里重新查询数据并填充图表。

void CChartDemoDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { RefreshChartData(); } CDialogEx::OnTimer(nIDEvent); } void CChartDemoDlg::RefreshChartData() { // 从数据库获取最新数据,填充到数组之后,复用前面的ShowLineChart逻辑 ShowLineChart(); }

要注意一点:MSChart控件在改变行列数之后会重建内部表,如果你只是刷新数值,行数列数不变,直接重复SetData即可,不需要重新设置ChartType和标签,这样刷新速度更快,也不会闪烁。反过来,如果数据源结构变了(比如分类数量变了),就必须把行列数一并更新。实测下来,MSChart对短时间内频繁重绘的支撑够用,几百毫秒刷一次曲线没有明显卡顿;如果数据量大到几千行,建议降低刷新频率,或者在内存里攒一批数据再一次性SetData,否则界面会明显掉帧。

5.2 点击图表实现联动交互

如果你做过Web端图表,肯定熟悉ECharts的鼠标悬浮提示、点击高亮这些交互。MSChart作为老控件,交互能力没有那么丰富,但也不至于完全不可用。MSChart控件提供了Click、OnSeriesSelected等事件,通过MFC的事件接收机制可以捕获。最常用的场景是“鼠标点了哪一列,下方文本区显示对应数据”:在对话框上给MSChart添加事件处理函数(类向导里找到MSChart的事件列表),选择Click,然后在处理函数里通过GetRow和GetColumn获取当前点击的行列位置,再去查询详细数据。

void CChartDemoDlg::OnClickMsChart() { short row = m_Chart.GetRow(); short col = m_Chart.GetColumn(); CString strInfo; strInfo.Format(_T("当前选中:第%d行,第%d列"), row, col); SetDlgItemText(IDC_EDIT_INFO, strInfo); }

这里GetRow和GetColumn返回的是MSChart内部数据表的行列坐标,不是像素坐标。如果你只是想在鼠标所在位置显示浮动数值,MSChart原生并没有直接接口,需要自己处理鼠标Move和坐标换算,那就要结合GetPlotRect等接口做坐标映射了。这个属于高阶玩法,市面上也有很多人直接用第三方控件或者封装好的MFC图表类替代,但如果你只想在现有MSChart基础上做轻量联动,事件处理就够了。

5.3 用同一套方法封装三种图表切换

最后分享一个我自己的工程习惯:把前面的功能封装成一个方法,用枚举值控制图表类型。界面三个按钮分别调用同一个方法,内部判断类型走不同逻辑:

void CChartDemoDlg::UpdateChart(int chartType) { switch (chartType) { case 1: ShowBarChart(); break; case 2: ShowLineChart(); break; case 3: ShowPieChart(); break; } }

这个封装的收益在做完三个图表后特别明显。客户如果临时要求换配色、加图例、调标题,所有改动都集中在对应的ShowXxxChart函数里,不会散落在各个按钮事件中。我在多个项目里都用这个套路,别人接手读代码也直观。顺带说一句,如果你后续要支持3D效果,MSChart还提供了三维柱状图和三维饼图类型,只需要改一下SetChartType的数值,这个扩展空间也够日常使用了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 7:28:03

基于STC89C52的T9拼音输入法设计与实现——从矩阵键盘到汉字显示

简介&#xff1a;T9输入法通过九宫格数字键组合预测单词&#xff0c;能在小键盘上显著减少按键次数&#xff0c;而将其移植到C51这类8位单片机&#xff0c;必须兼顾词库体积与检索速度。这份源码资源面向嵌入式开发者与单片机学习者&#xff0c;完整实现了T9预测算法的键值映射…

作者头像 李华
网站建设 2026/9/7 7:25:48

免费文件整理工具实战:DropIt、dupeGuru、PowerRename组合用法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华