前言
C# 开发中是否曾为一个既美观又实用的工业监控系统而苦恼?传统 WinForm 在现代 UI 需求面前已显力不从心,而企业对数据可视化的要求却日益提升。
本文将带大家使用 WPF + LiveCharts 技术栈,开发一套专业级、高颜值、高性能的工业监控界面。
我们将聚焦三大核心问题:如何设计现代化的工业风 UI、LiveCharts 图表的正确使用姿势、以及响应式布局的最佳实践。通过本文,大家将掌握一套可直接用于企业级项目的完整解决方案。
为什么选择 WPF + LiveCharts?
在开发工业监控类应用时,常面临以下挑战:
Chart 控件功能太基础:微软自带的 Chart 控件样式老旧,定制性差
第三方组件价格昂贵:商业图表库动辄数千甚至上万元
响应式布局困难:图表在不同分辨率下显示效果不佳
实时数据更新卡顿:大数据量下界面刷新迟滞
解决方案优势
WPF + LiveCharts 的组合有效解决了上述痛点:
免费开源:LiveCharts 完全免费,社区活跃
样式现代:支持丰富的动画与视觉效果
性能优秀:基于 WPF 渲染机制,流畅高效
高度定制:几乎所有样式、行为均可自定义
项目效果
项目架构
技术栈
<!-- 核心依赖包 -->
<PackageReference Include="LiveCharts.Wpf" Version="0.9.7" />
<PackageReference Include="LiveCharts" Version="0.9.7" />
核心功能实现
响应式布局设计
关键技巧:使用 Grid 的星号(*)布局实现真正的响应式适配。
<!-- 核心布局代码 -->
<Grid Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/><!-- 自适应高度 -->
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/><!-- 自适应宽度 -->
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 图表区域 -->
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource CardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/><!-- 标题固定高度 -->
<RowDefinition Height="*"/> <!-- 图表自适应 -->
</Grid.RowDefinitions>
<!-- 图表内容 -->
</Grid>
</Border>
</Grid>
实战经验:避免使用 StackPanel,它无法实现真正的比例自适应。Grid 配合星号才是响应式布局的王道!
LiveCharts 图表配置
// ViewModel中的图表初始化
private void InitializeCharts()
{
// 配置数据映射
var dayConfig = Mappers.Xy<ChartDataPoint>()
.X((dayModel, index) => index)
.Y(dayModel => dayModel.Value);
Charting.For<ChartDataPoint>(dayConfig);
// 温度趋势图配置
TemperatureSeries = new SeriesCollection
{
new LineSeries
{
Title = "温度",
Values = new ChartValues<ChartDataPoint>(),
Stroke = new SolidColorBrush(Color.FromRgb(52, 152, 219)),
Fill = Brushes.Transparent,
StrokeThickness = 2,
PointGeometry = null, // 去掉数据点,性能更好
LineSmoothness = 0.3 // 平滑曲线效果
}
};
}
实时数据更新机制
// 高性能的数据更新方案
private void UpdateChartData()
{
var tempValues = TemperatureSeries[0].Values;
tempValues.Add(new ChartDataPoint { Value = Temperature });
// 🔥 性能优化:限制数据点数量
if (tempValues.Count > 10)
tempValues.RemoveAt(0);
// 更新时间轴标签
TimeLabels.RemoveAt(0);
TimeLabels.Add(DateTime.Now.ToString("HH:mm"));
}
常见坑点:不要无限制地添加数据点,超过 100 个点性能会明显下降!
UI 样式定义
<!-- 卡片样式定义 -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="Background" Value="#FF2C3E50"/>
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect BlurRadius="10" ShadowDepth="3"
Color="#FF000000" Opacity="0.3"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ModernButtonStyle" TargetType="Button">
<Setter Property="Background" Value="#FF3498DB"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}"
CornerRadius="5">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#FF2980B9"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
完整实现
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using LiveCharts;
using LiveCharts.Configurations;
using LiveCharts.Wpf;
namespaceAppWpfMonitoringSystem
{
publicclassMainViewModel : INotifyPropertyChanged
{
privatereadonly DispatcherTimer _dataTimer;
privatereadonly Random _random;
privatebool _isMonitoring;
// 实时数据
privatedouble _temperature = 25.0;
privatedouble _pressure = 1.2;
privatedouble _flowRate = 15.5;
privatedouble _power = 12.8;
// 系统信息
privatedouble _cpuUsage = 45;
privatedouble _memoryUsage = 68;
privatestring _networkLatency = "15ms";
privatestring _connectedDevices = "8/10";
privatestring _runningTime = "02:15:30";
// 界面状态
privatestring _currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
privatestring _statusMessage = "系统正常运行";
privatestring _monitoringStatus = "监控中";
privatestring _dataCount = "数据点: 1,234";
// 参数设置
privatedouble _tempUpperLimit = 80;
privatedouble _pressureUpperLimit = 25;
privatedouble _dataInterval = 5;
public MainViewModel()
{
_random = new Random();
// 初始化数据集合
DeviceStatuses = new ObservableCollection<DeviceStatus>();
AlarmMessages = new ObservableCollection<AlarmMessage>();
// 初始化图表数据
InitializeCharts();
// 初始化设备状态
InitializeDeviceStatuses();
// 初始化数据更新定时器
_dataTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(DataInterval)
};
_dataTimer.Tick += DataTimer_Tick;
}
#region 属性
publicstring CurrentTime
{
get => _currentTime;
set => SetProperty(ref _currentTime, value);
}
publicdouble Temperature
{
get => _temperature;
set => SetProperty(ref _temperature, value);
}
publicdouble Pressure
{
get => _pressure;
set => SetProperty(ref _pressure, value);
}
publicdouble FlowRate
{
get => _flowRate;
set => SetProperty(ref _flowRate, value);
}
publicdouble Power
{
get => _power;
set => SetProperty(ref _power, value);
}
publicdouble CpuUsage
{
get => _cpuUsage;
set => SetProperty(ref _cpuUsage, value);
}
publicdouble MemoryUsage
{
get => _memoryUsage;
set => SetProperty(ref _memoryUsage, value);
}
publicstring NetworkLatency
{
get => _networkLatency;
set => SetProperty(ref _networkLatency, value);
}
publicstring ConnectedDevices
{
get => _connectedDevices;
set => SetProperty(ref _connectedDevices, value);
}
publicstring RunningTime
{
get => _runningTime;
set => SetProperty(ref _runningTime, value);
}
publicstring StatusMessage
{
get => _statusMessage;
set => SetProperty(ref _statusMessage, value);
}
publicstring MonitoringStatus
{
get => _monitoringStatus;
set => SetProperty(ref _monitoringStatus, value);
}
publicstring DataCount
{
get => _dataCount;
set => SetProperty(ref _dataCount, value);
}
publicdouble TempUpperLimit
{
get => _tempUpperLimit;
set => SetProperty(ref _tempUpperLimit, value);
}
publicdouble PressureUpperLimit
{
get => _pressureUpperLimit;
set => SetProperty(ref _pressureUpperLimit, value);
}
publicdouble DataInterval
{
get => _dataInterval;
set
{
if (SetProperty(ref _dataInterval, value))
{
_dataTimer.Interval = TimeSpan.FromSeconds(value);
}
}
}
public ObservableCollection<DeviceStatus> DeviceStatuses { get; }
public ObservableCollection<AlarmMessage> AlarmMessages { get; }
// 图表系列
public SeriesCollection TemperatureSeries { get; set; }
public SeriesCollection PressureSeries { get; set; }
public SeriesCollection FlowRateSeries { get; set; }
public SeriesCollection PowerDistributionSeries { get; set; }
public List<string> TimeLabels { get; set; }
#endregion
#region 方法
public void UpdateCurrentTime()
{
CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
public void StartMonitoring()
{
if (!_isMonitoring)
{
_isMonitoring = true;
_dataTimer.Start();
MonitoringStatus = "监控中";
StatusMessage = "监控已启动";
AddAlarmMessage("系统启动", "监控系统已启动", "#FF27AE60");
}
}
public void StopMonitoring()
{
if (_isMonitoring)
{
_isMonitoring = false;
_dataTimer.Stop();
MonitoringStatus = "已停止";
StatusMessage = "监控已停止";
AddAlarmMessage("系统停止", "监控系统已停止", "#FFFFC107");
}
}
public void ExportData()
{
// 这里实现数据导出逻辑
StatusMessage = "正在导出数据...";
// 模拟导出过程
Task.Run(async () =>
{
await Task.Delay(2000);
Application.Current.Dispatcher.Invoke(() =>
{
StatusMessage = "数据导出完成";
AddAlarmMessage("数据导出", "数据已成功导出到文件", "#FF3498DB");
});
});
}
private void InitializeCharts()
{
var dayConfig = Mappers.Xy<ChartDataPoint>()
.X((dayModel, index) => index)
.Y(dayModel => dayModel.Value);
var mapper = dayConfig;
Charting.For<ChartDataPoint>(mapper);
// 初始化时间标签
TimeLabels = new List<string>();
for (int i = 0; i < 20; i++)
{
TimeLabels.Add(DateTime.Now.AddMinutes(-20 + i).ToString("HH:mm"));
}
// 温度图表
TemperatureSeries = new SeriesCollection
{
new LineSeries
{
Title = "温度",
Values = new ChartValues<ChartDataPoint>(),
Stroke = new SolidColorBrush(Color.FromRgb(52, 152, 219)),
Fill = Brushes.Transparent,
StrokeThickness = 3,
PointGeometry = DefaultGeometries.Circle,
PointGeometrySize = 6
}
};
// 压力图表
PressureSeries = new SeriesCollection
{
new LineSeries
{
Title = "压力",
Values = new ChartValues<ChartDataPoint>(),
Stroke = new SolidColorBrush(Color.FromRgb(39, 174, 96)),
Fill = Brushes.Transparent,
StrokeThickness = 3,
PointGeometry = DefaultGeometries.Square,
PointGeometrySize = 6
}
};
// 流量图表
FlowRateSeries = new SeriesCollection
{
new LineSeries
{
Title = "流量",
Values = new ChartValues<ChartDataPoint>(),
Stroke = new SolidColorBrush(Color.FromRgb(243, 156, 18)),
Fill = Brushes.Transparent,
StrokeThickness = 3,
PointGeometry = DefaultGeometries.Diamond,
PointGeometrySize = 6
}
};
// 功率分布饼图
PowerDistributionSeries = new SeriesCollection
{
new PieSeries
{
Title = "泵1",
Values = new ChartValues<double> {35},
DataLabels = true,
Fill = new SolidColorBrush(Color.FromRgb(52, 152, 219))
},
new PieSeries
{
Title = "泵2",
Values = new ChartValues<double> {25},
DataLabels = true,
Fill = new SolidColorBrush(Color.FromRgb(39, 174, 96))
},
new PieSeries
{
Title = "压缩机",
Values = new ChartValues<double> {30},
DataLabels = true,
Fill = new SolidColorBrush(Color.FromRgb(243, 156, 18))
},
new PieSeries
{
Title = "其他",
Values = new ChartValues<double> {10},
DataLabels = true,
Fill = new SolidColorBrush(Color.FromRgb(149, 165, 166))
}
};
// 初始化历史数据
InitializeHistoricalData();
}
private void InitializeHistoricalData()
{
var tempValues = TemperatureSeries[0].Values;
var pressureValues = PressureSeries[0].Values;
var flowValues = FlowRateSeries[0].Values;
for (int i = 0; i < 20; i++)
{
tempValues.Add(new ChartDataPoint { Value = 20 + _random.NextDouble() * 15 });
pressureValues.Add(new ChartDataPoint { Value = 1 + _random.NextDouble() * 3 });
flowValues.Add(new ChartDataPoint { Value = 10 + _random.NextDouble() * 15 });
}
}
private void InitializeDeviceStatuses()
{
DeviceStatuses.Add(new DeviceStatus { DeviceName = "主泵1", Status = "运行", StatusColor = "#FF27AE60" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "主泵2", Status = "运行", StatusColor = "#FF27AE60" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "备用泵", Status = "待机", StatusColor = "#FFFFC107" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "压缩机", Status = "运行", StatusColor = "#FF27AE60" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "冷却塔", Status = "运行", StatusColor = "#FF27AE60" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "控制阀A", Status = "开启", StatusColor = "#FF3498DB" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "控制阀B", Status = "关闭", StatusColor = "#FF95A5A6" });
DeviceStatuses.Add(new DeviceStatus { DeviceName = "传感器1", Status = "故障", StatusColor = "#FFE74C3C" });
}
private void DataTimer_Tick(object sender, EventArgs e)
{
if (!_isMonitoring) return;
// 更新实时数据
UpdateRealTimeData();
// 更新图表数据
UpdateChartData();
// 更新系统信息
UpdateSystemInfo();
// 检查告警条件
CheckAlarms();
}
private void UpdateRealTimeData()
{
Temperature = 20 + _random.NextDouble() * 30;
Pressure = 1 + _random.NextDouble() * 4;
FlowRate = 10 + _random.NextDouble() * 20;
Power = 8 + _random.NextDouble() * 15;
}
private void UpdateChartData()
{
// 更新温度图表
var tempValues = TemperatureSeries[0].Values;
tempValues.Add(new ChartDataPoint { Value = Temperature });
if (tempValues.Count > 20)
tempValues.RemoveAt(0);
// 更新压力图表
var pressureValues = PressureSeries[0].Values;
pressureValues.Add(new ChartDataPoint { Value = Pressure });
if (pressureValues.Count > 20)
pressureValues.RemoveAt(0);
// 更新流量图表
var flowValues = FlowRateSeries[0].Values;
flowValues.Add(new ChartDataPoint { Value = FlowRate });
if (flowValues.Count > 20)
flowValues.RemoveAt(0);
// 更新时间标签
TimeLabels.RemoveAt(0);
TimeLabels.Add(DateTime.Now.ToString("HH:mm"));
}
private void UpdateSystemInfo()
{
CpuUsage = 30 + _random.NextDouble() * 40;
MemoryUsage = 50 + _random.NextDouble() * 30;
NetworkLatency = $"{10 + _random.Next(20)}ms";
// 更新数据计数
var count = 1234 + (int)(DateTime.Now.Ticks / TimeSpan.TicksPerSecond % 1000);
DataCount = $"数据点: {count:N0}";
}
private void CheckAlarms()
{
// 温度告警
if (Temperature > TempUpperLimit * 0.8)
{
AddAlarmMessage("温度告警",
$"温度过高: {Temperature:F1}°C (上限: {TempUpperLimit}°C)",
"#FFFF6B35");
}
// 压力告警
if (Pressure > PressureUpperLimit * 0.8)
{
AddAlarmMessage("压力告警",
$"压力过高: {Pressure:F1} Bar (上限: {PressureUpperLimit} Bar)",
"#FFFF6B35");
}
// 随机生成一些告警
if (_random.NextDouble() < 0.05) // 5%概率
{
var alarms = new[]
{
("设备告警", "传感器1通信超时", "#FFE74C3C"),
("系统提示", "数据备份完成", "#FF27AE60"),
("维护提醒", "设备A需要定期维护", "#FFFFC107")
};
var alarm = alarms[_random.Next(alarms.Length)];
AddAlarmMessage(alarm.Item1, alarm.Item2, alarm.Item3);
}
}
private void AddAlarmMessage(string type, string message, string color)
{
AlarmMessages.Insert(0, new AlarmMessage
{
Type = type,
Message = message,
Timestamp = DateTime.Now.ToString("HH:mm:ss"),
AlarmColor = color
});
// 限制告警消息数量
if (AlarmMessages.Count > 20)
{
AlarmMessages.RemoveAt(AlarmMessages.Count - 1);
}
}
#endregion
#region INotifyPropertyChanged
publicevent PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protectedbool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
returnfalse;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
returntrue;
}
#endregion
}
}
项目解析
配色方案
动画效果
// 图表动画配置
AnimationsSpeed="0:0:0.5" // 0.5秒动画时长
LineSmoothness = 0.3 // 线条平滑度
响应式设计
最小分辨率:1200×600,兼容主流显示器
自适应布局:图表区域根据窗口大小自动调整
字体缩放:支持不同 DPI 设置
性能优化技巧
数据量控制
// 限制历史数据点数量
if (tempValues.Count > 10)
tempValues.RemoveAt(0);
UI 虚拟化
<!-- ListView 使用虚拟化 -->
<ListView VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
定时器优化
// 避免过于频繁的更新
_dataTimer.Interval = TimeSpan.FromSeconds(1); // 1秒更新一次
总结
通过本文的实战演练,我们成功打造了一个颜值与功能并存的工业监控界面。
核心要点总结如下:
三个关键技术点
1、响应式布局:Grid + 星号布局是响应式设计的基石
2、性能优化:限制数据点数量,避免内存泄漏
3、现代化 UI:卡片式设计 + 阴影效果 + 动画过渡
这套方案不仅适用于工业监控场景,还可轻松扩展至金融数据看板、网站流量分析、IoT 设备管理等各类数据可视化需求,具备极强的通用性与可复用性。
关键词
#WPF、#LiveCharts、#工业监控、#响应式布局、#数据可视化、C#、#MVVM、性能优化、#UI设计、#实时数据
作者:技术老小子

C# 工业巡检系统:集成海康摄像头、轨道机与 OpenCV 的实战方案
C# + SkiaSharp 批量处理上千张图片,分钟级高效完成
一套工具搞定工业通讯全场景?让 PLC、Modbus、TCP 通信一目了然
.NET 8 + WPF 的 Modbus 智能温湿监控系统
.NET 8 微服务框架长什么样?集成 AI 智能体、自动调度与实时通信
觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力

