前言
C# 开发中是否曾为一个既美观又实用的工业监控系统而苦恼?传统 WinForm 在现代 UI 需求面前已显力不从心,而企业对数据可视化的要求却日益提升。
本文将带大家使用WPF + LiveCharts技术栈,开发一套专业级、高颜值、高性能的工业监控界面。
我们将聚焦三大核心问题:如何设计现代化的工业风 UI、LiveCharts 图表的正确使用姿势、以及响应式布局的最佳实践。通过本文,大家将掌握一套可直接用于企业级项目的完整解决方案。
为什么选择 WPF + LiveCharts?
在开发工业监控类应用时,常面临以下挑战:
Chart 控件功能太基础:微软自带的 Chart 控件样式老旧,定制性差
第三方组件价格昂贵:商业图表库动辄数千甚至上万元
响应式布局困难:图表在不同分辨率下显示效果不佳
实时数据更新卡顿:大数据量下界面刷新迟滞
解决方案优势
WPF + LiveCharts的组合有效解决了上述痛点:
免费开源:LiveCharts 完全免费,社区活跃
样式现代:支持丰富的动画与视觉效果
性能优秀:基于 WPF 渲染机制,流畅高效
高度定制:几乎所有样式、行为均可自定义
项目效果
项目架构
技术栈
<!-- 核心依赖包 --> <PackageReferenceInclude="LiveCharts.Wpf"Version="0.9.7" /> <PackageReferenceInclude="LiveCharts"Version="0.9.7" />核心功能实现
响应式布局设计
关键技巧:使用 Grid 的星号(*)布局实现真正的响应式适配。
<!-- 核心布局代码 --> <GridGrid.Column="1"Margin="5"> <Grid.RowDefinitions> <RowDefinitionHeight="*"/><!-- 自适应高度 --> <RowDefinitionHeight="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinitionWidth="*"/><!-- 自适应宽度 --> <ColumnDefinitionWidth="*"/> </Grid.ColumnDefinitions> <!-- 图表区域 --> <BorderGrid.Row="0"Grid.Column="0"Style="{StaticResource CardStyle}"> <Grid> <Grid.RowDefinitions> <RowDefinitionHeight="Auto"/><!-- 标题固定高度 --> <RowDefinitionHeight="*"/> <!-- 图表自适应 --> </Grid.RowDefinitions> <!-- 图表内容 --> </Grid> </Border> </Grid>实战经验:避免使用StackPanel,它无法实现真正的比例自适应。Grid配合星号才是响应式布局的王道!
LiveCharts 图表配置
// ViewModel中的图表初始化 privatevoidInitializeCharts() { // 配置数据映射 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 // 平滑曲线效果 } }; }实时数据更新机制
// 高性能的数据更新方案 privatevoidUpdateChartData() { 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 样式定义
<!-- 卡片样式定义 --> <Stylex:Key="CardStyle"TargetType="Border"> <SetterProperty="Background"Value="#FF2C3E50"/> <SetterProperty="CornerRadius"Value="8"/> <SetterProperty="Margin"Value="5"/> <SetterProperty="Effect"> <Setter.Value> <DropShadowEffectBlurRadius="10"ShadowDepth="3" Color="#FF000000"Opacity="0.3"/> </Setter.Value> </Setter> </Style> <Stylex:Key="ModernButtonStyle"TargetType="Button"> <SetterProperty="Background"Value="#FF3498DB"/> <SetterProperty="Foreground"Value="White"/> <SetterProperty="BorderThickness"Value="0"/> <SetterProperty="Template"> <Setter.Value> <ControlTemplateTargetType="Button"> <BorderBackground="{TemplateBinding Background}" CornerRadius="5"> <ContentPresenterHorizontalAlignment="Center" VerticalAlignment="Center"/> </Border> <ControlTemplate.Triggers> <TriggerProperty="IsMouseOver"Value="True"> <SetterProperty="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; publicMainViewModel() { _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 方法 publicvoidUpdateCurrentTime() { CurrentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); } publicvoidStartMonitoring() { if (!_isMonitoring) { _isMonitoring = true; _dataTimer.Start(); MonitoringStatus = "监控中"; StatusMessage = "监控已启动"; AddAlarmMessage("系统启动", "监控系统已启动", "#FF27AE60"); } } publicvoidStopMonitoring() { if (_isMonitoring) { _isMonitoring = false; _dataTimer.Stop(); MonitoringStatus = "已停止"; StatusMessage = "监控已停止"; AddAlarmMessage("系统停止", "监控系统已停止", "#FFFFC107"); } } publicvoidExportData() { // 这里实现数据导出逻辑 StatusMessage = "正在导出数据..."; // 模拟导出过程 Task.Run(async () => { await Task.Delay(2000); Application.Current.Dispatcher.Invoke(() => { StatusMessage = "数据导出完成"; AddAlarmMessage("数据导出", "数据已成功导出到文件", "#FF3498DB"); }); }); } privatevoidInitializeCharts() { 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(); } privatevoidInitializeHistoricalData() { 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 }); } } privatevoidInitializeDeviceStatuses() { 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" }); } privatevoidDataTimer_Tick(object sender, EventArgs e) { if (!_isMonitoring) return; // 更新实时数据 UpdateRealTimeData(); // 更新图表数据 UpdateChartData(); // 更新系统信息 UpdateSystemInfo(); // 检查告警条件 CheckAlarms(); } privatevoidUpdateRealTimeData() { Temperature = 20 + _random.NextDouble() * 30; Pressure = 1 + _random.NextDouble() * 4; FlowRate = 10 + _random.NextDouble() * 20; Power = 8 + _random.NextDouble() * 15; } privatevoidUpdateChartData() { // 更新温度图表 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")); } privatevoidUpdateSystemInfo() { 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}"; } privatevoidCheckAlarms() { // 温度告警 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); } } privatevoidAddAlarmMessage(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; protectedvirtualvoidOnPropertyChanged([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 } }项目解析
配色方案
主色调:深蓝灰 (
#2C3E50) —— 专业沉稳强调色:蓝色 (
#3498DB) —— 科技感
成功色:绿色 (#27AE60) —— 状态正常警告色:橙色 (
#F39C12) —— 需要注意错误色:红色 (
#E74C3C) —— 紧急状态
动画效果
// 图表动画配置 AnimationsSpeed="0:0:0.5" // 0.5秒动画时长 LineSmoothness = 0.3 // 线条平滑度响应式设计
最小分辨率:1200×600,兼容主流显示器
自适应布局:图表区域根据窗口大小自动调整
字体缩放:支持不同 DPI 设置
性能优化技巧
数据量控制
// 限制历史数据点数量 if (tempValues.Count > 10) tempValues.RemoveAt(0);UI 虚拟化
<!-- ListView 使用虚拟化 --> <ListViewVirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">定时器优化
// 避免过于频繁的更新 _dataTimer.Interval = TimeSpan.FromSeconds(1); // 1秒更新一次总结
通过本文的实战演练,我们成功打造了一个颜值与功能并存的工业监控界面。
核心要点总结如下:
三个关键技术点
1、响应式布局:Grid+ 星号布局是响应式设计的基石
2、性能优化:限制数据点数量,避免内存泄漏
3、现代化 UI:卡片式设计 + 阴影效果 + 动画过渡
这套方案不仅适用于工业监控场景,还可轻松扩展至金融数据看板、网站流量分析、IoT 设备管理等各类数据可视化需求,具备极强的通用性与可复用性。
关键词
#WPF、#LiveCharts、#工业监控、#响应式布局、#数据可视化、C#、#MVVM、性能优化、#UI设计、#实时数据
最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠]社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:技术老小子
出处:mp.weixin.qq.com/s/03yiLmPx-hSLGGknaYr30A
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!
- EOF -
技术群:添加小编微信dotnet999
公众号:dotnet讲堂