编辑
2026-04-27
C#
00

目录

用 LiveCharts 2 在 WinForms 里画出第一张柱状图,核心只需三步:NuGet 安装 LiveChartsCore.SkiaSharpView.WinForms、拖一个 CartesianChart 控件、给 Series 赋一个 ColumnSeries<T> 就能跑起来,全程不用写一行 GDI+ 绘图代码。
🔍 为什么是 LiveCharts 2,而不是别的?
🧱 环境准备:搭好地基再盖楼
🎯 第一版:最小可运行示例(Hello BarChart)
🚀 第二版:加上多组对比与数据绑定
💎 第三版:实时数据更新与性能优化
⚠️ 避雷指南:我踩过的那些坑
📚 技术洞察三句话
🎓 进阶学习路径
🗣️ 一起聊聊
🎯 写在最后

用 LiveCharts 2 在 WinForms 里画出第一张柱状图,核心只需三步:NuGet 安装 LiveChartsCore.SkiaSharpView.WinForms、拖一个 CartesianChart 控件、给 Series 赋一个 ColumnSeries<T> 就能跑起来,全程不用写一行 GDI+ 绘图代码。

咱们做 C# 桌面开发的兄弟,多多少少都有过这么一段回忆:领导拍着桌子说"这个月报表得加个图表",然后你打开工具箱找 Chart 控件——嗯,.NET Framework 时代还有个 System.Windows.Forms.DataVisualization.Charting,到了 .NET 6/7/8 直接就没了。去网上一搜,要么是十几年前的老控件配色土到掉渣,要么是商业库动辄几千刀一个授权,要么就是官方文档写得像天书,照着抄都跑不起来。

我自己在去年做一个工业数据采集系统的时候也踩过这坑,老项目用的是 MSChart,迁移到 .NET 8 之后直接报错,最后选型选了 LiveCharts 2(也就是社区里常说的 LVC)。这东西基于 SkiaSharp 渲染,速度快、样式漂亮、API 也算干净,关键是免费开源、跨框架(WinForms / WPF / MAUI / Avalonia 都能用)。

这篇文章咱们不整那些花里胡哨的理论,就聚焦一件事:从零开始,在 WinForms 里画出你的第一张柱状图。读完之后你能拿到:一份可以直接复制运行的完整代码、三种由浅入深的实现方式、以及几个我踩过的坑的避雷指南。


🔍 为什么是 LiveCharts 2,而不是别的?

在正式动手之前,我想花一点篇幅说清楚选型这件事。因为我见过太多同学上来就 Ctrl+C、Ctrl+V,跑起来一出问题就懵了,根源就是没搞懂自己用的是什么。

WinForms 图表库现在市面上主流的有这么几个:

图表库渲染方式.NET 8 支持授权上手难度
MSChart(老牌)GDI+需手动引用包免费
LiveCharts 2SkiaSharp原生支持MIT
ScottPlotGDI+/Skia原生支持MIT
商业控件(如 DevExpress)GDI+/DirectX支持付费中高

LiveCharts 2 最大的优势是跨框架一致性——你在 WinForms 里写的配置代码,挪到 WPF 项目里几乎不用改。这对做多端桌面应用的团队来说太香了。另外它的动画效果是原生内置的,柱子从零开始"长"出来的那种丝滑感,用 MSChart 想做得手撸计时器,LVC 里就是一个属性的事儿。

当然它也不是完美的。我在实际使用中发现它的内存占用比 MSChart 稍高(大概高 20~30%),如果你的场景是嵌入式工控机内存只有 2G,可能还得权衡一下。


🧱 环境准备:搭好地基再盖楼

先把前置条件列清楚,省得大家半路卡壳。

测试环境说明:

  • 操作系统:Windows 11 23H2
  • IDE:Visual Studio 2022(17.9 及以上)
  • .NET SDK:.NET 8.0
  • LiveCharts 2 版本:2.0.0-rc5.4(截至撰文时的稳定预览版)

注意:LiveCharts 2 目前仍处于 rc 阶段,NuGet 上搜索时必须勾选"包括预发行版本",否则你会搜不到包。这是 99% 新手第一次踩的坑。

新建一个 WinForms 项目,目标框架选 .NET 8.0,然后打开 NuGet 包管理器,安装:

LiveChartsCore.SkiaSharpView.WinForms

这一个包会自动把 LiveChartsCoreSkiaSharp、以及 WinForms 适配层全部带进来,不用你一个个装。


🎯 第一版:最小可运行示例(Hello BarChart)

咱们先追求"能跑起来",再谈"跑得好看"。新建一个 Form,拖一个 CartesianChart 控件到窗体上(工具箱里找不到的话,先编译一次项目,控件就会自动出现)。

然后在 Form1.cs 里写下这段代码:

csharp
using LiveChartsCore; using LiveChartsCore.SkiaSharpView; namespace AppLiveChart04 { public partial class Form1 : Form { public Form1() { InitializeComponent(); InitChart(); } private void InitChart() { // 1. 准备数据:各城市 Q1 销售额(单位:万元) var values = new double[] { 128, 256, 189, 342, 215 }; // 2. 构造柱状图系列 cartesianChart1.Series = new ISeries[] { new ColumnSeries<double> { Values = values, Name = "销售额" } }; // 3. 配置 X 轴标签 cartesianChart1.XAxes = new[] { new Axis { Labels = new[] { "北京", "上海", "广州", "深圳", "杭州" }, LabelsRotation = 0 } }; // 4. 配置 Y 轴 cartesianChart1.YAxes = new[] { new Axis { Name = "金额(万元)", MinLimit = 0 } }; } } }

image.png

按 F5 运行,你会看到一张带柔和动画的柱状图,五根柱子依次"弹"出来。到这一步,你就已经完成了第一张 LiveCharts 2 柱状图

这段代码有几个关键点值得拎出来说

  • ColumnSeries<T> 里的 T 是数据类型,doubleint、甚至自定义对象都行
  • Series 属性接受的是 ISeries[] 数组,意味着你可以同时画多组柱子做对比
  • XAxesYAxes 也是数组,理论上你能做双 Y 轴图表

🚀 第二版:加上多组对比与数据绑定

光画一组柱子远远不够用。实际项目里,我们经常要做同比、环比、不同维度对比。这一版咱们把"Q1 和 Q2 销售对比"做出来,并且把硬编码的数组换成可绑定的 ViewModel,方便后续和数据库对接。

csharp
using LiveChartsCore; using LiveChartsCore.SkiaSharpView; using LiveChartsCore.SkiaSharpView.Painting; using SkiaSharp; namespace FirstBarChartDemo { // 定义一个简单的数据模型 public class SalesRecord { public string City { get; set; } = string.Empty; public double Q1 { get; set; } public double Q2 { get; set; } } public partial class Form2 : Form { private readonly List<SalesRecord> _records = new() { new SalesRecord { City = "北京", Q1 = 128, Q2 = 156 }, new SalesRecord { City = "上海", Q1 = 256, Q2 = 289 }, new SalesRecord { City = "广州", Q1 = 189, Q2 = 210 }, new SalesRecord { City = "深圳", Q1 = 342, Q2 = 378 }, new SalesRecord { City = "杭州", Q1 = 215, Q2 = 248 } }; public Form2() { InitializeComponent(); BuildChart(); } private void BuildChart() { // 通过 Mapping 让 LiveCharts 知道怎么从对象里取值 cartesianChart1.Series = new ISeries[] { new ColumnSeries<SalesRecord> { Name = "Q1", Values = _records, // 关键:告诉图表用对象的哪个属性作为 Y 值 Mapping = (record, index) => new(index, record.Q1), Fill = new SolidColorPaint(SKColors.SteelBlue), DataLabelsPaint = new SolidColorPaint(SKColors.Black), DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top }, new ColumnSeries<SalesRecord> { Name = "Q2", Values = _records, Mapping = (record, index) => new(index, record.Q2), Fill = new SolidColorPaint(SKColors.DarkOrange), DataLabelsPaint = new SolidColorPaint(SKColors.Black), DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.Top } }; cartesianChart1.XAxes = new[] { new Axis { Labels = _records.Select(r => r.City).ToArray(), LabelsRotation = 0, TextSize = 14 } }; cartesianChart1.YAxes = new[] { new Axis { Name = "销售额(万元)", NameTextSize = 14, MinLimit = 0, Labeler = value => value.ToString("N0") } }; // 图例位置 cartesianChart1.LegendPosition = LiveChartsCore.Measure.LegendPosition.Top; } } }

image.png

这一版相比第一版有几个实质性升级

  • 数据对象化:用 SalesRecord 类承载业务数据,接近真实项目结构
  • 多系列对比:两组 ColumnSeries 并排显示,对比关系一目了然
  • 显式着色:通过 Fill 指定颜色,不再依赖默认主题
  • 数据标签DataLabelsPaint 让每根柱子顶部显示数值
  • 数值格式化Labeler 用于把 Y 轴刻度格式化成千分位

我在实际项目中发现,Mapping 这个属性是很多人卡住的地方。官方文档一笔带过,但它的本质是"告诉图表库怎么从你的对象里把 X、Y 坐标抠出来"。记住这个心智模型就不会错。


💎 第三版:实时数据更新与性能优化

前面两版都是静态数据。但真实项目里,柱状图经常需要实时刷新——比如监控大屏、实时订单统计、设备产量看板。这一版咱们做一个"每秒刷新一次"的动态柱状图。

csharp
using LiveChartsCore; using LiveChartsCore.SkiaSharpView; using System.Collections.ObjectModel; namespace FirstBarChartDemo { public partial class Form3 : Form { // 用 ObservableCollection 让图表自动感知数据变化 private readonly ObservableCollection<double> _liveValues = new(); private readonly System.Windows.Forms.Timer _timer = new(); private readonly Random _random = new(); private const int MaxPoints = 20; public Form3() { InitializeComponent(); InitData(); BuildChart(); StartTimer(); } private void InitData() { for (int i = 0; i < MaxPoints; i++) { _liveValues.Add(_random.Next(50, 300)); } } private void BuildChart() { cartesianChart1.Series = new ISeries[] { new ColumnSeries<double> { Values = _liveValues, Name = "实时产量" } }; cartesianChart1.XAxes = new[] { new Axis { Name = "时间片", MinLimit = 0, MaxLimit = MaxPoints } }; // 关键优化:关闭动画可以显著降低高频刷新的 CPU 占用 cartesianChart1.AnimationsSpeed = TimeSpan.FromMilliseconds(300); } private void StartTimer() { _timer.Interval = 1000; _timer.Tick += (s, e) => { // 移除最老的一个,追加最新的一个,形成滚动效果 _liveValues.RemoveAt(0); _liveValues.Add(_random.Next(50, 300)); }; _timer.Start(); } protected override void OnFormClosed(FormClosedEventArgs e) { _timer.Stop(); _timer.Dispose(); base.OnFormClosed(e); } } }

image.png

这一版有几个生产环境级别的考量

1. ObservableCollection 的威力

LiveCharts 2 会自动订阅 ObservableCollectionCollectionChanged 事件。你只要增删元素,图表就会自己刷新,不用手动调用任何重绘方法。这个机制比 WPF 的 DataBinding 还要省心。

2. 高频刷新的性能陷阱

我之前把 Timer 间隔设成 100ms,结果 CPU 飙到 15%。后来排查发现,默认动画时长是 800ms,当刷新频率高于动画时长时,新动画会打断旧动画,造成大量无效计算。

性能对比数据(测试环境:i5-12400、16G 内存、20 个数据点):

刷新间隔动画时长CPU 占用视觉体验
1000ms800ms~2%流畅
500ms800ms~7%有卡顿
100ms800ms~15%明显卡顿
100ms0ms(关闭动画)~3%硬切,但不卡

结论刷新频率 > 动画时长时,果断关闭动画AnimationsSpeed = TimeSpan.Zero)。

3. 资源释放别忘了

Timer 必须在 OnFormClosed 里显式 Dispose,否则窗体关了,定时器还在后台空跑,早晚内存泄漏给你看。


⚠️ 避雷指南:我踩过的那些坑

坑1:安装包时没勾预发行版本

前面提过一次,这里再强调:LiveCharts 2 还是 rc 状态,默认搜不到。

坑2:控件找不到

安装完包之后,工具箱里不会立刻出现 CartesianChart必须先编译一次项目

坑3:中文字体显示成方块

SkiaSharp 默认字体不支持中文。解决办法是给 Axis 配置 LabelsPaint

csharp
new Axis { Labels = new[] { "北京", "上海" }, LabelsPaint = new SolidColorPaint(SKColors.Black) { SKTypeface = SKTypeface.FromFamilyName("Microsoft YaHei") } }

坑4:多个 Form 都用图表时首次加载慢

LiveCharts 2 初始化 SkiaSharp 渲染上下文有一次性开销,大概 200~400ms。解决思路是应用启动时预热——在 Splash 界面加载阶段就 new 一个 CartesianChart 实例并丢弃,后续窗体打开就秒开了。


📚 技术洞察三句话

  • 图表库选型的本质,不是比谁功能多,而是比谁和你的技术栈贴合度高
  • 动画很美,但生产环境里它首先是性能敏感项,其次才是体验加分项
  • Mapping 是 LiveCharts 2 里最容易被忽略、但最能体现其设计哲学的机制

🎓 进阶学习路径

如果你想把 LiveCharts 2 玩得更溜,建议按这个顺序深入:

  1. 折线图(LineSeries) —— 时序数据可视化的基础
  2. 饼图 / 环形图(PieSeries) —— 构成占比分析
  3. 散点图与热力图 —— 相关性与密度分布
  4. 地理图(GeoMap) —— 带地理维度的数据呈现
  5. 自定义 Paint / 自定义 VisualElement —— 深度定制样式

官方仓库地址:LiveCharts2 on GitHub,里面有非常全的 samples 工程,几乎每种图表都有 WinForms 版本的示例,强烈建议 clone 下来对照学习


🗣️ 一起聊聊

本文讨论话题:

  1. 在你的项目里,图表控件的选型踩过哪些坑?是选了商业库,还是自己基于 GDI+ 撸轮子?
  2. 你觉得 WinForms 在 2026 年做数据可视化,还有哪些值得继续投入的场景?

欢迎在评论区聊聊你的实践经验,也欢迎把这篇文章分享给正在做桌面端可视化的同事。


🎯 写在最后

回到开头说的三步走:装包、拖控件、配 Series。LiveCharts 2 把柱状图这件事做到了"五分钟入门、一小时进阶、一天能上生产"的程度。

本文从最小示例讲到多系列对比,再到实时动态刷新,覆盖了柱状图最常见的三类场景。代码都经过 .NET 8 + LiveCharts 2.0.0-rc5.4 实际验证,复制到你的项目里改改数据源就能用。

如果你刚好在做 .NET 桌面端的数据可视化需求,希望这篇能帮你少走一些弯路。接下来如果有机会,咱们可以继续聊聊折线图的实时高频刷新(万级数据点),那才是真正考验图表库性能的硬仗。

相关标签C#开发 WinForms LiveCharts2 数据可视化 性能优化

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!