编辑
2025-12-04
C#
00

目录

🎯 为什么选择WPF + LiveCharts?
痛点分析
解决方案优势
💡 项目架构设计
技术栈选择
🔧 核心功能实现
响应式布局设计
LiveCharts图表配置
实时数据更新机制
UI样式
🚀 核心代码实现
MainViewModel完整实现
完整的XAML布局
🎨 设计亮点解析
配色方案
动画效果
3. 响应式设计
⚡ 性能优化技巧
数据量控制
UI虚拟化
定时器优化
🎯 总结与思考
💬 互动时间

作为C#开发者,你是否曾经为了做一个炫酷的数据可视化界面而苦恼?特别是工业监控系统,既要美观又要实用,传统的WinForms已经无法满足现代化的UI需求。今天,我将带你用WPF + LiveCharts打造一个专业级工业监控界面,不仅颜值在线,功能也相当强大。

本文将解决三个核心问题:如何设计现代化的工业风UILiveCharts图表的正确使用姿势响应式布局的最佳实践。看完这篇文章,你将掌握一套完整的企业级监控界面开发方案!

🎯 为什么选择WPF + LiveCharts?

痛点分析

很多开发者在做数据可视化时都遇到过这些问题:

  • Chart控件功能太基础:微软自带的Chart控件样式老旧,定制性差
  • 第三方组件价格昂贵:动辄几千上万的商业组件让人望而却步
  • 响应式布局困难:图表在不同分辨率下显示效果差强人意
  • 实时数据更新卡顿:数据量大时界面更新不流畅

解决方案优势

LiveCharts + WPF 这个组合完美解决了上述痛点:

免费开源:LiveCharts完全免费,社区活跃

样式现代:支持各种炫酷的动画效果

性能优秀:底层基于WPF绘制,性能卓越

高度定制:几乎所有样式都可以自定义

💡 项目架构设计

技术栈选择

C#
// 核心依赖包 <PackageReference Include="LiveCharts.Wpf" Version="0.9.7" /> <PackageReference Include="LiveCharts" Version="0.9.7" />

🔧 核心功能实现

响应式布局设计

关键技巧:使用Grid的星号布局实现真正的响应式

XML
<!-- 核心布局代码 --> <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图表配置

C#
// 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 // 平滑曲线效果 } }; }

实时数据更新机制

C#
// 高性能的数据更新方案 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样式

XML
<!-- 卡片样式定义 --> <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>

🚀 核心代码实现

MainViewModel完整实现

C#
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; namespace AppWpfMonitoringSystem { public class MainViewModel : INotifyPropertyChanged { private readonly DispatcherTimer _dataTimer; private readonly Random _random; private bool _isMonitoring; // 实时数据 private double _temperature = 25.0; private double _pressure = 1.2; private double _flowRate = 15.5; private double _power = 12.8; // 系统信息 private double _cpuUsage = 45; private double _memoryUsage = 68; private string _networkLatency = "15ms"; private string _connectedDevices = "8/10"; private string _runningTime = "02:15:30"; // 界面状态 private string _currentTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); private string _statusMessage = "系统正常运行"; private string _monitoringStatus = "监控中"; private string _dataCount = "数据点: 1,234"; // 参数设置 private double _tempUpperLimit = 80; private double _pressureUpperLimit = 25; private double _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 属性 public string CurrentTime { get => _currentTime; set => SetProperty(ref _currentTime, value); } public double Temperature { get => _temperature; set => SetProperty(ref _temperature, value); } public double Pressure { get => _pressure; set => SetProperty(ref _pressure, value); } public double FlowRate { get => _flowRate; set => SetProperty(ref _flowRate, value); } public double Power { get => _power; set => SetProperty(ref _power, value); } public double CpuUsage { get => _cpuUsage; set => SetProperty(ref _cpuUsage, value); } public double MemoryUsage { get => _memoryUsage; set => SetProperty(ref _memoryUsage, value); } public string NetworkLatency { get => _networkLatency; set => SetProperty(ref _networkLatency, value); } public string ConnectedDevices { get => _connectedDevices; set => SetProperty(ref _connectedDevices, value); } public string RunningTime { get => _runningTime; set => SetProperty(ref _runningTime, value); } public string StatusMessage { get => _statusMessage; set => SetProperty(ref _statusMessage, value); } public string MonitoringStatus { get => _monitoringStatus; set => SetProperty(ref _monitoringStatus, value); } public string DataCount { get => _dataCount; set => SetProperty(ref _dataCount, value); } public double TempUpperLimit { get => _tempUpperLimit; set => SetProperty(ref _tempUpperLimit, value); } public double PressureUpperLimit { get => _pressureUpperLimit; set => SetProperty(ref _pressureUpperLimit, value); } public double 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 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action onChanged = null) { if (EqualityComparer<T>.Default.Equals(backingStore, value)) return false; backingStore = value; onChanged?.Invoke(); OnPropertyChanged(propertyName); return true; } #endregion } }

完整的XAML布局

XML
<Window x:Class="AppWpfMonitoringSystem.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:AppWpfMonitoringSystem" mc:Ignorable="d" xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf" Title="MainWindow" Height="450" Width="800"> <Window.Resources> <!-- 定义样式 --> <Style x:Key="ModernButtonStyle" TargetType="Button"> <Setter Property="Background" Value="#FF3498DB"/> <Setter Property="Foreground" Value="White"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Padding" Value="15,8"/> <Setter Property="Margin" Value="5"/> <Setter Property="FontSize" Value="14"/> <Setter Property="FontWeight" Value="SemiBold"/> <Setter Property="Cursor" Value="Hand"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Background="{TemplateBinding Background}" CornerRadius="5" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <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> <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="HeaderTextStyle" TargetType="TextBlock"> <Setter Property="Foreground" Value="White"/> <Setter Property="FontSize" Value="16"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="Margin" Value="15,10,15,5"/> </Style> <Style x:Key="ValueTextStyle" TargetType="TextBlock"> <Setter Property="Foreground" Value="#FF3498DB"/> <Setter Property="FontSize" Value="24"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> <Style x:Key="StatusIndicatorStyle" TargetType="Ellipse"> <Setter Property="Width" Value="15"/> <Setter Property="Height" Value="15"/> <Setter Property="Margin" Value="5"/> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="80"/> <RowDefinition Height="*"/> <RowDefinition Height="40"/> </Grid.RowDefinitions> <!-- 顶部工具栏 --> <Border Grid.Row="0" Background="#FF34495E" BorderBrush="#FF2C3E50" BorderThickness="0,0,0,2"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="20,0"> <TextBlock Text="工业监控系统" Foreground="White" FontSize="24" FontWeight="Bold" VerticalAlignment="Center"/> <TextBlock Text="{Binding CurrentTime}" Foreground="#FFBDC3C7" FontSize="14" VerticalAlignment="Center" Margin="30,0,0,0"/> </StackPanel> <StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,20,0"> <Button Content="开始监控" Style="{StaticResource ModernButtonStyle}" Background="#FF27AE60" Click="StartMonitoring_Click"/> <Button Content="停止监控" Style="{StaticResource ModernButtonStyle}" Background="#FFE74C3C" Click="StopMonitoring_Click"/> <Button Content="导出数据" Style="{StaticResource ModernButtonStyle}" Click="ExportData_Click"/> </StackPanel> </Grid> </Border> <!-- 主要内容区域 --> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="300"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="300"/> </Grid.ColumnDefinitions> <!-- 左侧状态面板 --> <ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto"> <StackPanel> <!-- 设备状态卡片 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="设备状态" Style="{StaticResource HeaderTextStyle}"/> <ItemsControl ItemsSource="{Binding DeviceStatuses}"> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Margin="15,5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <Ellipse Grid.Column="0" Fill="{Binding StatusColor}" Style="{StaticResource StatusIndicatorStyle}"/> <TextBlock Grid.Column="1" Text="{Binding DeviceName}" Foreground="White" VerticalAlignment="Center" Margin="10,0"/> <TextBlock Grid.Column="2" Text="{Binding Status}" Foreground="#FFBDC3C7" FontSize="12"/> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel> </Border> <!-- 实时数据卡片 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="实时数据" Style="{StaticResource HeaderTextStyle}"/> <Grid Margin="15"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="温度 (°C)" Foreground="White" FontSize="12"/> <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Temperature}" Style="{StaticResource ValueTextStyle}" FontSize="16"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="压力 (Bar)" Foreground="White" FontSize="12" Margin="0,10,0,0"/> <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Pressure}" Style="{StaticResource ValueTextStyle}" FontSize="16" Margin="0,10,0,0"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="流量 (L/min)" Foreground="White" FontSize="12" Margin="0,10,0,0"/> <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding FlowRate}" Style="{StaticResource ValueTextStyle}" FontSize="16" Margin="0,10,0,0"/> <TextBlock Grid.Row="3" Grid.Column="0" Text="功率 (kW)" Foreground="White" FontSize="12" Margin="0,10,0,0"/> <TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Power}" Style="{StaticResource ValueTextStyle}" FontSize="16" Margin="0,10,0,0"/> </Grid> </StackPanel> </Border> <!-- 告警信息 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="告警信息" Style="{StaticResource HeaderTextStyle}"/> <ListView ItemsSource="{Binding AlarmMessages}" Background="Transparent" BorderThickness="0" MaxHeight="200"> <ListView.ItemTemplate> <DataTemplate> <Grid Margin="5"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Ellipse Grid.Column="0" Fill="{Binding AlarmColor}" Width="8" Height="8" VerticalAlignment="Top" Margin="0,2,10,0"/> <StackPanel Grid.Column="1"> <TextBlock Text="{Binding Message}" Foreground="White" FontSize="12" TextWrapping="Wrap"/> <TextBlock Text="{Binding Timestamp}" Foreground="#FFBDC3C7" FontSize="10"/> </StackPanel> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackPanel> </Border> </StackPanel> </ScrollViewer> <!-- 中央图表区域 --> <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}" Margin="5"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="温度趋势" Style="{StaticResource HeaderTextStyle}"/> <lvc:CartesianChart Grid.Row="1" Series="{Binding TemperatureSeries}" LegendLocation="None" AnimationsSpeed="0:0:0.5" Margin="10,0,10,10" Background="Transparent"> <lvc:CartesianChart.AxisX> <lvc:Axis Title="时间" Foreground="White" FontSize="10" Labels="{Binding TimeLabels}"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisX> <lvc:CartesianChart.AxisY> <lvc:Axis Title="温度 (°C)" Foreground="White" FontSize="10"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisY> </lvc:CartesianChart> </Grid> </Border> <!-- 压力趋势图 --> <Border Grid.Row="0" Grid.Column="1" Style="{StaticResource CardStyle}" Margin="5"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="压力趋势" Style="{StaticResource HeaderTextStyle}"/> <lvc:CartesianChart Grid.Row="1" Series="{Binding PressureSeries}" LegendLocation="None" AnimationsSpeed="0:0:0.5" Margin="10,0,10,10" Background="Transparent"> <lvc:CartesianChart.AxisX> <lvc:Axis Title="时间" Foreground="White" FontSize="10" Labels="{Binding TimeLabels}"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisX> <lvc:CartesianChart.AxisY> <lvc:Axis Title="压力 (Bar)" Foreground="White" FontSize="10"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisY> </lvc:CartesianChart> </Grid> </Border> <!-- 流量趋势图 --> <Border Grid.Row="1" Grid.Column="0" Style="{StaticResource CardStyle}" Margin="5"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="流量趋势" Style="{StaticResource HeaderTextStyle}"/> <lvc:CartesianChart Grid.Row="1" Series="{Binding FlowRateSeries}" LegendLocation="None" AnimationsSpeed="0:0:0.5" Margin="10,0,10,10" Background="Transparent"> <lvc:CartesianChart.AxisX> <lvc:Axis Title="时间" Foreground="White" FontSize="10" Labels="{Binding TimeLabels}"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisX> <lvc:CartesianChart.AxisY> <lvc:Axis Title="流量 (L/min)" Foreground="White" FontSize="10"> <lvc:Axis.Separator> <lvc:Separator StrokeThickness="1" StrokeDashArray="2" Stroke="#404040"/> </lvc:Axis.Separator> </lvc:Axis> </lvc:CartesianChart.AxisY> </lvc:CartesianChart> </Grid> </Border> <!-- 功率分布饼图 --> <Border Grid.Row="1" Grid.Column="1" Style="{StaticResource CardStyle}" Margin="5"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="功率分布" Style="{StaticResource HeaderTextStyle}"/> <lvc:PieChart Grid.Row="1" Series="{Binding PowerDistributionSeries}" LegendLocation="Bottom" InnerRadius="30" Margin="10" Background="Transparent"> <lvc:PieChart.ChartLegend> <lvc:DefaultLegend BulletSize="8" Foreground="White" FontSize="9"/> </lvc:PieChart.ChartLegend> </lvc:PieChart> </Grid> </Border> </Grid> <!-- 右侧控制面板 --> <ScrollViewer Grid.Column="2" VerticalScrollBarVisibility="Auto"> <StackPanel> <!-- 系统信息 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="系统信息" Style="{StaticResource HeaderTextStyle}"/> <Grid Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <TextBlock Grid.Row="0" Grid.Column="0" Text="CPU使用率:" Foreground="White" FontSize="12"/> <ProgressBar Grid.Row="0" Grid.Column="1" Value="{Binding CpuUsage}" Height="15" Margin="10,0,0,5" Foreground="#FF3498DB"/> <TextBlock Grid.Row="1" Grid.Column="0" Text="内存使用:" Foreground="White" FontSize="12" Margin="0,5,0,0"/> <ProgressBar Grid.Row="1" Grid.Column="1" Value="{Binding MemoryUsage}" Height="15" Margin="10,5,0,5" Foreground="#FF27AE60"/> <TextBlock Grid.Row="2" Grid.Column="0" Text="网络延迟:" Foreground="White" FontSize="12" Margin="0,5,0,0"/> <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding NetworkLatency}" Foreground="#FF3498DB" FontSize="12" Margin="10,5,0,0"/> <TextBlock Grid.Row="3" Grid.Column="0" Text="连接设备:" Foreground="White" FontSize="12" Margin="0,5,0,0"/> <TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding ConnectedDevices}" Foreground="#FF27AE60" FontSize="12" Margin="10,5,0,0"/> <TextBlock Grid.Row="4" Grid.Column="0" Text="运行时间:" Foreground="White" FontSize="12" Margin="0,5,0,0"/> <TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding RunningTime}" Foreground="#FFBDC3C7" FontSize="12" Margin="10,5,0,0"/> </Grid> </StackPanel> </Border> <!-- 设备控制 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="设备控制" Style="{StaticResource HeaderTextStyle}"/> <UniformGrid Columns="2" Margin="15"> <Button Content="启动泵1" Style="{StaticResource ModernButtonStyle}" Background="#FF27AE60"/> <Button Content="停止泵1" Style="{StaticResource ModernButtonStyle}" Background="#FFE74C3C"/> <Button Content="启动泵2" Style="{StaticResource ModernButtonStyle}" Background="#FF27AE60"/> <Button Content="停止泵2" Style="{StaticResource ModernButtonStyle}" Background="#FFE74C3C"/> <Button Content="开启阀门" Style="{StaticResource ModernButtonStyle}" Background="#FFF39C12"/> <Button Content="关闭阀门" Style="{StaticResource ModernButtonStyle}" Background="#FF95A5A6"/> </UniformGrid> </StackPanel> </Border> <!-- 参数设置 --> <Border Style="{StaticResource CardStyle}"> <StackPanel> <TextBlock Text="参数设置" Style="{StaticResource HeaderTextStyle}"/> <Grid Margin="15"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Margin="0,5"> <TextBlock Text="温度上限 (°C)" Foreground="White" FontSize="12"/> <Slider Value="{Binding TempUpperLimit}" Minimum="0" Maximum="200" TickFrequency="10" IsSnapToTickEnabled="True" Margin="0,5"/> <TextBlock Text="{Binding TempUpperLimit}" Foreground="#FF3498DB" HorizontalAlignment="Center"/> </StackPanel> <StackPanel Grid.Row="1" Margin="0,10,0,5"> <TextBlock Text="压力上限 (Bar)" Foreground="White" FontSize="12"/> <Slider Value="{Binding PressureUpperLimit}" Minimum="0" Maximum="50" TickFrequency="5" IsSnapToTickEnabled="True" Margin="0,5"/> <TextBlock Text="{Binding PressureUpperLimit}" Foreground="#FF3498DB" HorizontalAlignment="Center"/> </StackPanel> <StackPanel Grid.Row="2" Margin="0,10,0,5"> <TextBlock Text="数据采集间隔 (秒)" Foreground="White" FontSize="12"/> <Slider Value="{Binding DataInterval}" Minimum="1" Maximum="60" TickFrequency="5" IsSnapToTickEnabled="True" Margin="0,5"/> <TextBlock Text="{Binding DataInterval}" Foreground="#FF3498DB" HorizontalAlignment="Center"/> </StackPanel> <Button Grid.Row="3" Content="应用设置" Style="{StaticResource ModernButtonStyle}" Margin="0,15,0,0"/> </Grid> </StackPanel> </Border> </StackPanel> </ScrollViewer> </Grid> <!-- 底部状态栏 --> <Border Grid.Row="2" Background="#FF34495E" BorderBrush="#FF2C3E50" BorderThickness="0,2,0,0"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <TextBlock Grid.Column="0" Text="{Binding StatusMessage}" Foreground="White" VerticalAlignment="Center" Margin="20,0"/> <TextBlock Grid.Column="1" Text="{Binding MonitoringStatus}" Foreground="#FF27AE60" VerticalAlignment="Center" Margin="20,0"/> <TextBlock Grid.Column="2" Text="{Binding DataCount}" Foreground="#FF3498DB" VerticalAlignment="Center" Margin="20,0"/> <TextBlock Grid.Column="3" Text="v1.0.0" Foreground="#FFBDC3C7" VerticalAlignment="Center" Margin="20,0"/> </Grid> </Border> </Grid> </Window>
C#
using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; namespace AppWpfMonitoringSystem { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private readonly DispatcherTimer _timer; private readonly MainViewModel _viewModel; public MainWindow() { InitializeComponent(); _viewModel = new MainViewModel(); DataContext = _viewModel; // 初始化定时器 _timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; _timer.Tick += Timer_Tick; _timer.Start(); } private void Timer_Tick(object sender, EventArgs e) { _viewModel.UpdateCurrentTime(); } private void StartMonitoring_Click(object sender, RoutedEventArgs e) { _viewModel.StartMonitoring(); } private void StopMonitoring_Click(object sender, RoutedEventArgs e) { _viewModel.StopMonitoring(); } private void ExportData_Click(object sender, RoutedEventArgs e) { _viewModel.ExportData(); } } }

image.png

🎨 设计亮点解析

配色方案

  • 主色调:深蓝灰 (#2C3E50) - 专业沉稳
  • 强调色:蓝色 (#3498DB) - 科技感
  • 成功色:绿色 (#27AE60) - 状态正常
  • 警告色:橙色 (#F39C12) - 需要注意
  • 错误色:红色 (#E74C3C) - 紧急状态

动画效果

C#
// 图表动画配置 AnimationsSpeed="0:0:0.5" // 0.5秒动画时长 LineSmoothness = 0.3 // 线条平滑度

3. 响应式设计

  • 最小分辨率:1200x600,兼容主流显示器
  • 自适应布局:图表区域根据窗口大小自动调整
  • 字体缩放:支持不同DPI设置

⚡ 性能优化技巧

数据量控制

C#
// 限制历史数据点数量 if (tempValues.Count > 10) tempValues.RemoveAt(0);

UI虚拟化

XML
<!-- ListView使用虚拟化 --> <ListView VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">

定时器优化

C#
// 避免过于频繁的更新 _dataTimer.Interval = TimeSpan.FromSeconds(1); // 1秒更新一次

🎯 总结与思考

通过本文的实战演练,我们成功打造了一个颜值与功能并存的工业监控界面。核心要点总结:

🔥 三个关键技术点

  1. 响应式布局:Grid + 星号布局是响应式设计的基石
  2. 性能优化:限制数据点数量,避免内存泄漏
  3. 现代化UI:卡片式设计 + 阴影效果 + 动画过渡

这套方案不仅适用于工业监控,还可以扩展到金融数据看板网站流量分析IoT设备管理等各种数据可视化场景。

💬 互动时间

问题1:你在项目中使用过哪些图表库?LiveCharts相比其他方案有什么优势?

问题2:对于大数据量的实时更新场景,你还有什么更好的优化方案?

如果这篇文章对你有帮助,请转发给更多同行!我们一起用技术改变世界!


关注我,获取更多C#开发实战技巧和最佳实践!

相关信息

通过网盘分享的文件:AppWpfMonitoringSystem.zip 链接: https://pan.baidu.com/s/1oLWx6Tl7BtOa2cJCbRwyGw?pwd=aqvn 提取码: aqvn --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

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