一、项目核心信息
1. 技术栈
-
框架:WPF(Windows Presentation Foundation)
-
数据存储:SQLite + SqlSugar(ORM 框架)
-
图表展示:LiveCharts.Wpf(开源图表库)
-
功能定位:可视化展示年度销量数据,支持动态追加月度数据并持久化到数据库。
2. 核心文件说明
| 文件 / 类 | 作用 |
|---|---|
SalesRecord.cs |
销量数据实体类,映射 SQLite 中的 SalesRecord 表 |
DbHelper.cs |
数据库操作工具类,封装 SqlSugar 连接、建表、数据增查逻辑 |
UCLineChart.xaml |
折线图用户控件布局,包含图表容器和追加数据按钮 |
UCLineChart.xaml.cs |
图表逻辑处理,包括数据初始化、图表渲染、动态追加数据等核心逻辑 |
3.项目结构梳理
LineChart2/
├─ DbHelper.cs // Sugar ORM数据库操作封
├─ SalesRecord.cs // 销量数据实体类
├─ UCLineChart.xaml // 折线图用户控件XAML
├─ UCLineChart.xaml.cs // 折线图逻辑(整合数据库)
├─ MainWindow.xaml // 主窗口
└─ SalesData.db // SQLite数据库文件(运行后生成)
二、核心代码解析与使用示例
1. 数据库层使用
(1)SalesRecord 实体(数据映射)
// 核心特性说明
[SugarTable("SalesRecord")] // 指定数据库表名
public class SalesRecord
{
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)] // 主键+自增
public int Id { get; set; }
[SugarColumn(ColumnName = "Year")] // 映射数据库字段名(可选,默认属性名)
public int SalesYear { get; set; } // 年份
public int Month { get; set; } // 月份(1-12)
public double SalesValue { get; set; } // 销量值
public DateTime CreateTime { get; set; } = DateTime.Now; // 数据创建时间
}
使用示例:创建一条 2024 年 6 月的销量数据
var newRecord = new SalesRecord
{
SalesYear = 2024,
Month = 6,
SalesValue = 180.5 // 销量值支持小数,图表会自动格式化
};
(2)DbHelper 工具类(数据库操作)
核心功能 1:初始化数据库连接
// 静态构造函数自动初始化,无需手动调用
// 数据库文件路径:程序运行目录/SalesData.db
// 关键配置:自动关闭连接、从特性读取主键配置
核心功能 2:数据操作封装(使用示例)
// 1. 批量插入数据(初始化默认数据时使用)
var defaultData = new List<SalesRecord>
{
new SalesRecord { SalesYear = 2024, Month = 1, SalesValue = 120 },
new SalesRecord { SalesYear = 2024, Month = 2, SalesValue = 150 }
};
DbHelper.BatchInsertSalesData(defaultData.ToArray());
// 2. 插入单条数据并返回自增ID
int newId = DbHelper.InsertSalesData(newRecord);
Console.WriteLine($"新增数据ID:{newId}");
// 3. 查询指定年份数据
List<SalesRecord> 2024Data = DbHelper.GetSalesDataByYear(2024);
foreach (var item in 2024Data)
{
Console.WriteLine($"{item.Month}月销量:{item.SalesValue}");
}
// 4. 查询所有数据
List<SalesRecord> allData = DbHelper.GetAllSalesData();
2. 图表层使用
(1)XAML 布局(UCLineChart.xaml)
<!-- 核心图表控件配置 -->
<lvc:CartesianChart x:Name="chart"
Series="{Binding SeriesCollection}"
LegendLocation="Top"
Background="White">
<!-- Y轴:销量(件),格式化显示整数 -->
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="销量(件)"
LabelFormatter="{Binding YFormatter}"
Foreground="#333333"/>
</lvc:CartesianChart.AxisY>
<!-- X轴:月份,绑定标签数组 -->
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="月份"
Labels="{Binding Labels}"
Foreground="#333333"/>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
(2)图表逻辑(UCLineChart.xaml.cs)
核心功能 1:初始化图表数据(从数据库加载)
private void InitChartDataFromDb()
{
SeriesCollection = new SeriesCollection();
var allSalesData = DbHelper.GetAllSalesData();
// 无数据时初始化默认数据
if (!allSalesData.Any())
{
InitDefaultDataAndWriteToDb(); // 写入2023/2024年1-5月默认数据
allSalesData = DbHelper.GetAllSalesData();
}
// 按年份分组构建折线
var groupedData = allSalesData.GroupBy(r => r.SalesYear);
foreach (var group in groupedData)
{
int year = group.Key;
var values = group.OrderBy(r => r.Month).Select(r => r.SalesValue).ToList();
// 创建折线系列(不同年份区分样式)
LineSeries series = new LineSeries
{
Title = $"{year}年",
Values = new ChartValues<double>(values),
LineSmoothness = 0.3, // 折线平滑度
StrokeThickness = 2, // 线条粗细
PointGeometrySize = 8 // 数据点大小
};
// 样式区分:2024年蓝色圆形,2023年红色方形
series.Stroke = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointForeground = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointGeometry = year == 2024 ? DefaultGeometries.Circle : DefaultGeometries.Square;
SeriesCollection.Add(series);
}
// 构建X轴标签(1-当前最大月份)
_currentMaxMonth = allSalesData.Max(r => r.Month);
Labels = Enumerable.Range(1, _currentMaxMonth).Select(m => $"{m}月").ToArray();
}
核心功能 2:动态追加数据(按钮点击事件)
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
// 1. 月份自增
_currentMaxMonth++;
int newMonth = _currentMaxMonth;
// 2. 生成随机销量(100-200)
double sales2024 = _random.Next(100, 200);
double sales2023 = _random.Next(100, 200);
// 3. 写入数据库(持久化)
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2024, Month = newMonth, SalesValue = sales2024 });
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2023, Month = newMonth, SalesValue = sales2023 });
// 4. 更新图表数据点
SeriesCollection[0].Values.Add(sales2024); // 2024年系列
SeriesCollection[1].Values.Add(sales2023); // 2023年系列
// 5. 更新X轴标签(修复数组直接扩容的CS0206错误)
string[] tempLabels = Labels;
Array.Resize(ref tempLabels, tempLabels.Length + 1);
tempLabels[tempLabels.Length - 1] = $"{newMonth}月";
Labels = tempLabels;
// 6. 刷新UI绑定
chart.AxisX[0].Labels = Labels;
// 7. 更新按钮文本(追加7月→追加8月...)
(sender as Button).Content = $"追加{newMonth + 1}月数据";
}
3. 完整使用示例(主窗口集成)
步骤 1:在 MainWindow.xaml 中引用控件
<Window x:Class="LineChart2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LineChart2"
Title="销量折线图" Height="500" Width="800">
<Grid>
<!-- 引用自定义折线图控件 -->
<local:UCLineChart/>
</Grid>
</Window>
步骤 2:运行效果
-
首次运行:
-
自动创建
SalesData.db数据库文件; -
写入 2023/2024 年 1-5 月默认数据;
-
图表显示两条折线(2023 年红色方形点,2024 年蓝色圆形点),X 轴标签为 "1 月 - 5 月"。
-
-
点击 "追加 6 月数据" 按钮:
-
数据库新增 2023/2024 年 6 月随机销量数据;
-
图表自动追加 6 月数据点,X 轴标签更新为 "1 月 - 6 月";
-
按钮文本变为 "追加 7 月数据",重复点击可继续追加后续月份。
-
三、环境配置与依赖
1. NuGet 包安装
| 包名 | 版本(建议) | 作用 |
|---|---|---|
| SqlSugarCore | 5.1.4.100 | SQLite 数据库 ORM 操作 |
| System.Data.SQLite | 1.0.118 | SQLite 数据提供程序 |
| LiveCharts.Wpf | 0.97.0 | WPF 图表展示核心库 |
| LiveCharts | 0.97.0 | LiveCharts 基础核心库 |
2. 安装命令(Package Manager Console)
Install-Package SqlSugarCore -Version 5.1.4.100
Install-Package System.Data.SQLite -Version 1.0.118
Install-Package LiveCharts.Wpf -Version 0.97.0
四、扩展与定制示例
1. 定制折线样式
修改 InitChartDataFromDb 中 LineSeries 的配置:
LineSeries series = new LineSeries
{
Title = $"{year}年",
Values = new ChartValues<double>(values),
LineSmoothness = 0.8, // 更平滑的折线(0-1)
StrokeThickness = 3, // 加粗线条
PointGeometrySize = 10, // 放大数据点
Fill = Brushes.LightBlue // 折线下方填充颜色(2024年)
};
// 自定义颜色(如2025年用绿色三角形)
if (year == 2025)
{
series.Stroke = Brushes.Green;
series.PointForeground = Brushes.Green;
series.PointGeometry = DefaultGeometries.Triangle;
}
2. 扩展多年份数据
在 InitDefaultDataAndWriteToDb 中添加 2025 年数据:
// 2025年1-5月数据
double[] sales2025 = { 110, 140, 160, 170, 190 };
for (int i = 0; i < sales2025.Length; i++)
{
defaultData.Add(new SalesRecord
{
SalesYear = 2025,
Month = i + 1,
SalesValue = sales2025[i]
});
}
3. 限制最大月份(仅展示 12 个月)
修改 BtnAddData_Click 事件:
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
if (_currentMaxMonth >= 12)
{
MessageBox.Show("已追加到12月,无法继续追加!");
(sender as Button).IsEnabled = false;
return;
}
// 原有追加逻辑...
}
五、常见问题与解决
1. 图表无数据显示
-
检查数据库文件是否生成(程序运行目录 / SalesData.db);
-
调试
DbHelper.GetAllSalesData()是否返回数据; -
确认
SeriesCollection已赋值且DataContext = this;执行。
2. 追加数据后 X 轴标签不更新
-
确保执行
chart.AxisX[0].Labels = Labels;刷新绑定; -
检查
Labels数组扩容逻辑是否正确(必须通过临时变量中转)。
3. 数据库连接失败
-
确认
System.Data.SQLite包安装完整,适配项目平台(x86/x64/AnyCPU); -
检查连接字符串格式:
Data Source={dbPath};为 SQLite 标准格式,路径无特殊字符。
六、运行流程总结
-
程序启动 → DbHelper 初始化 SQLite 连接 → 自动创建 SalesRecord 表;
-
UCLineChart 加载 → 从数据库查询数据 → 无数据则写入默认 1-5 月数据;
-
按年份分组渲染折线图 → 显示 1-5 月销量;
-
点击追加按钮 → 月份 + 1 → 生成随机销量 → 写入数据库 → 更新图表数据和标签;
-
重复点击可追加到 12 月,数据持久化到数据库,重启程序后仍能加载历史数据。
七、完整代码
DbHelper.cs
using SqlSugar;
using LineChart2;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
namespace LineChart2
{
public class DbHelper
{
// Sugar数据库连接实例(单例)
public static SqlSugarClient Db { get; private set; }
static DbHelper()
{
// 初始化连接
InitDb();
}
/// <summary>
/// 初始化SQLite连接
/// </summary>
private static void InitDb()
{
// 获取数据库文件路径(项目根目录)
string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SalesData.db");
Db = new SqlSugarClient(new ConnectionConfig
{
ConnectionString = $"Data Source={dbPath};", // SQLite连接字符串
DbType = DbType.Sqlite, // 数据库类型
IsAutoCloseConnection = true, // 自动关闭连接
InitKeyType = InitKeyType.Attribute // 从特性读取主键/自增配置
});
// 初始化数据表(不存在则创建)
Db.CodeFirst.InitTables(typeof(SalesRecord));
// 开启日志(调试用,生产可关闭)
Db.Aop.OnLogExecuting = (sql, pars) =>
{
Console.WriteLine($"SQL:{sql} \r\n参数:{string.Join(",", pars.Select(p => p.ParameterName + "=" + p.Value))}");
};
}
#region 销量数据操作封装
/// <summary>
/// 批量插入销量数据
/// </summary>
public static void BatchInsertSalesData(SalesRecord[] records)
{
Db.Insertable(records).ExecuteCommand();
}
/// <summary>
/// 插入单条销量数据
/// </summary>
public static int InsertSalesData(SalesRecord record)
{
return Db.Insertable(record).ExecuteReturnIdentity();
}
/// <summary>
/// 根据年份查询销量数据
/// </summary>
public static List<SalesRecord> GetSalesDataByYear(int year)
{
return Db.Queryable<SalesRecord>()
.Where(r => r.SalesYear == year)
.OrderBy(r => r.Month)
.ToList();
}
/// <summary>
/// 查询所有年份的销量数据
/// </summary>
public static List<SalesRecord> GetAllSalesData()
{
return Db.Queryable<SalesRecord>()
.OrderBy(r => r.SalesYear)
.ToList();
}
#endregion
}
}
Model.cs
using SqlSugar;
using System;
namespace LineChart2
{
/// <summary>
/// 销量数据表实体
/// </summary>
[SugarTable("SalesRecord")]
public class SalesRecord
{
/// <summary>
/// 主键(自增)
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 年份(2023/2024)
/// </summary>
[SugarColumn(ColumnName = "Year")]
public int SalesYear { get; set; }
/// <summary>
/// 月份(1-12)
/// </summary>
public int Month { get; set; }
/// <summary>
/// 销量数值
/// </summary>
public double SalesValue { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}
UCLineChart.xaml
<UserControl x:Class="LineChart2.UCLineChart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:LineChart2"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<lvc:CartesianChart x:Name="chart"
Series="{Binding SeriesCollection}"
LegendLocation="Top"
Background="White">
<lvc:CartesianChart.AxisY>
<lvc:Axis Title="销量(件)"
LabelFormatter="{Binding YFormatter}"
Foreground="#333333"/>
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="月份"
Labels="{Binding Labels}"
Foreground="#333333"/>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
<!-- 追加数据按钮 -->
<Button Grid.Row="1"
Content="追加6月数据"
Width="120" Height="30"
Margin="10"
Click="BtnAddData_Click"/>
</Grid>
</UserControl>
UCLineChart.xaml.cs
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
using LineChart2;
using LineChart2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace LineChart2
{
public partial class UCLineChart : UserControl
{
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Func<double, string> YFormatter { get; set; }
// 模拟销量数据
private readonly Random _random = new Random();
// 当前最大月份(用于追加数据)
private int _currentMaxMonth = 5;
public UCLineChart()
{
InitializeComponent();
// 初始化图表数据(优先从数据库读取)
InitChartDataFromDb();
// Y轴格式化(显示整数)
YFormatter = value => $"{value:N0}";
DataContext = this;
}
/// <summary>
/// 从数据库初始化图表数据
/// </summary>
private void InitChartDataFromDb()
{
SeriesCollection = new SeriesCollection();
// 1. 查询所有销量数据
var allSalesData = DbHelper.GetAllSalesData();
// 2. 如果数据库无数据,初始化默认数据并写入数据库
if (!allSalesData.Any())
{
InitDefaultDataAndWriteToDb();
allSalesData = DbHelper.GetAllSalesData();
}
// 3. 按年份分组构建折线系列
var groupedData = allSalesData.GroupBy(r => r.SalesYear);
foreach (var group in groupedData)
{
int year = group.Key;
var values = group.OrderBy(r => r.Month).Select(r => r.SalesValue).ToList();
// 创建折线系列
LineSeries series = new LineSeries
{
Title = $"{year}年",
Values = new ChartValues<double>(values),
LineSmoothness = 0.3,
StrokeThickness = 2,
PointGeometrySize = 8
};
// 不同年份设置不同颜色
series.Stroke = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointForeground = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointGeometry = year == 2024 ? DefaultGeometries.Circle : DefaultGeometries.Square;
SeriesCollection.Add(series);
}
// 4. 构建X轴标签(1-当前最大月份)
_currentMaxMonth = allSalesData.Max(r => r.Month);
Labels = Enumerable.Range(1, _currentMaxMonth).Select(m => $"{m}月").ToArray();
}
/// <summary>
/// 初始化默认数据并写入数据库
/// </summary>
private void InitDefaultDataAndWriteToDb()
{
List<SalesRecord> defaultData = new List<SalesRecord>();
// 2024年1-5月数据
double[] sales2024 = { 120, 150, 110, 180, 160 };
for (int i = 0; i < sales2024.Length; i++)
{
defaultData.Add(new SalesRecord
{
SalesYear = 2024,
Month = i + 1,
SalesValue = sales2024[i]
});
}
// 2023年1-5月数据
double[] sales2023 = { 100, 130, 140, 150, 130 };
for (int i = 0; i < sales2023.Length; i++)
{
defaultData.Add(new SalesRecord
{
SalesYear = 2023,
Month = i + 1,
SalesValue = sales2023[i]
});
}
// 批量写入数据库
DbHelper.BatchInsertSalesData(defaultData.ToArray());
}
// 追加数据按钮点击事件
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
// 1. 月份+1
_currentMaxMonth++;
int newMonth = _currentMaxMonth;
// 2. 生成随机销量(100-200)
double sales2024 = _random.Next(100, 200);
double sales2023 = _random.Next(100, 200);
// 3. 写入数据库
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2024, Month = newMonth, SalesValue = sales2024 });
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2023, Month = newMonth, SalesValue = sales2023 });
// 4. 追加数据点到图表
SeriesCollection[0].Values.Add(sales2024);
SeriesCollection[1].Values.Add(sales2023);
// 5. 更新X轴标签(修复CS0206错误)
// 步骤1:将属性赋值给临时变量
string[] tempLabels = Labels;
// 步骤2:对临时变量执行数组扩容
Array.Resize(ref tempLabels, tempLabels.Length + 1);
// 步骤3:给扩容后的数组赋值新标签
tempLabels[tempLabels.Length - 1] = $"{newMonth}月";
// 步骤4:将临时变量赋值回属性
Labels = tempLabels;
// 6. 刷新绑定(触发UI更新)
chart.AxisX[0].Labels = Labels;
// 更新按钮文本(追加7月、8月...)
(sender as Button).Content = $"追加{newMonth + 1}月数据";
}
}
}
MainWindow.xaml
<Window x:Class="LineChart2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LineChart2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid Margin="10">
<!-- 自定义折线图用户控件 -->
<local:UCLineChart x:Name="ucLineChart"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="#F5F5F5"
BorderBrush="#E0E0E0"
BorderThickness="1"
/>
</Grid>
</Grid>
</Window>
效果展示:

