作为C#开发者,你是否曾经为了做一个炫酷的数据可视化界面而苦恼?特别是工业监控系统,既要美观又要实用,传统的WinForms已经无法满足现代化的UI需求。今天,我将带你用WPF + LiveCharts打造一个专业级工业监控界面,不仅颜值在线,功能也相当强大。
本文将解决三个核心问题:如何设计现代化的工业风UI、LiveCharts图表的正确使用姿势、响应式布局的最佳实践。看完这篇文章,你将掌握一套完整的企业级监控界面开发方案!
很多开发者在做数据可视化时都遇到过这些问题:
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配合星号才是王道!
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个点性能会明显下降!
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>
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
}
}
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();
}
}
}

C#// 图表动画配置
AnimationsSpeed="0:0:0.5" // 0.5秒动画时长
LineSmoothness = 0.3 // 线条平滑度
C#// 限制历史数据点数量
if (tempValues.Count > 10)
tempValues.RemoveAt(0);
XML<!-- ListView使用虚拟化 -->
<ListView VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling">
C#// 避免过于频繁的更新
_dataTimer.Interval = TimeSpan.FromSeconds(1); // 1秒更新一次
通过本文的实战演练,我们成功打造了一个颜值与功能并存的工业监控界面。核心要点总结:
🔥 三个关键技术点:
这套方案不仅适用于工业监控,还可以扩展到金融数据看板、网站流量分析、IoT设备管理等各种数据可视化场景。
问题1:你在项目中使用过哪些图表库?LiveCharts相比其他方案有什么优势?
问题2:对于大数据量的实时更新场景,你还有什么更好的优化方案?
如果这篇文章对你有帮助,请转发给更多同行!我们一起用技术改变世界!
关注我,获取更多C#开发实战技巧和最佳实践!
相关信息
通过网盘分享的文件:AppWpfMonitoringSystem.zip 链接: https://pan.baidu.com/s/1oLWx6Tl7BtOa2cJCbRwyGw?pwd=aqvn 提取码: aqvn --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!