大数跨境
0
0

C# + LiveCharts 工业监控界面,实时数据可视化实战

C# + LiveCharts 工业监控界面,实时数据可视化实战 DotNet技术匠
2025-12-05
0
导读:C# 工业监控界面开发实战:从零打造高性能LiveCharts可视化系统。

前言

C# 开发中是否曾为一个既美观又实用的工业监控系统而苦恼?传统 WinForm 在现代 UI 需求面前已显力不从心,而企业对数据可视化的要求却日益提升。

本文将带大家使用 WPF + LiveCharts 技术栈,开发一套专业级、高颜值、高性能的工业监控界面。

我们将聚焦三大核心问题:如何设计现代化的工业风 UILiveCharts 图表的正确使用姿势、以及响应式布局的最佳实践。通过本文,大家将掌握一套可直接用于企业级项目的完整解决方案。

为什么选择 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(52152219)),
            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 { getset; }
        public SeriesCollection PressureSeries { getset; }
        public SeriesCollection FlowRateSeries { getset; }
        public SeriesCollection PowerDistributionSeries { getset; }
        public List<string> TimeLabels { getset; }
        #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(52152219)),
                    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(3917496)),
                    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(24315618)),
                    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(52152219))
                },
                new PieSeries
                {
                    Title = "泵2",
                    Values = new ChartValues<double> {25},
                    DataLabels = true,
                    Fill = new SolidColorBrush(Color.FromRgb(3917496))
                },
                new PieSeries
                {
                    Title = "压缩机",
                    Values = new ChartValues<double> {30},
                    DataLabels = true,
                    Fill = new SolidColorBrush(Color.FromRgb(24315618))
                },
                new PieSeries
                {
                    Title = "其他",
                    Values = new ChartValues<double> {10},
                    DataLabels = true,
                    Fill = new SolidColorBrush(Color.FromRgb(149165166))
                }
            };

            // 初始化历史数据
            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(0new 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(thisnew PropertyChangedEventArgs(propertyName));
        }
        protectedbool SetProperty<T>(ref T backingStore, T value,
            [CallerMemberNamestring 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 使用虚拟化 -->
<ListView VirtualizingPanel.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
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!



END



方便大家交流、资源共享和共同成长
纯技术交流群,需要加入的小伙伴请扫码,并备注加群



推荐阅读






WPF + LiveCharts 实战开发工业设备监控看板
.NET 美观、功能强大的图表库 LiveCharts2
C# 轻量级工业通信: 实现配置驱动的 PLC 数据采集
WPF + HelixToolkit 的工业级钻包 3D 监控系统

快速搭建工业上位机?试试这个开源 WPF 数据采集框架

C# 工业巡检系统:集成海康摄像头、轨道机与 OpenCV 的实战方案

C# + SkiaSharp 批量处理上千张图片,分钟级高效完成

一套工具搞定工业通讯全场景?让 PLC、Modbus、TCP 通信一目了然

.NET 8 + WPF 的 Modbus 智能温湿监控系统

.NET 8 微服务框架长什么样?集成 AI 智能体、自动调度与实时通信

基于 JSON 配置的 .NET 桌面应用自动更新方案

基于 .NET 的可视化流程编辑工业视觉框架

Visual Studio 2026 上手体验,AI 懂你、界面清爽、协作无缝


觉得有收获?不妨分享让更多人受益

关注「DotNet技术匠」,共同提升技术实力


收藏
点赞
分享
在看

【声明】内容源于网络
0
0
DotNet技术匠
「DotNet技术匠」聚焦.NET核心,分享深度干货、实战技巧、最新资讯、优质资源,助你领跑技术赛道,赋能开发者成长。
内容 1715
粉丝 0
DotNet技术匠 「DotNet技术匠」聚焦.NET核心,分享深度干货、实战技巧、最新资讯、优质资源,助你领跑技术赛道,赋能开发者成长。
总阅读23
粉丝0
内容1.7k