WPF MVVM下LiveCharts2如何绑定动态计算属性实现图表自动更新
遵循MVVM模式时如何将图表绑定到动态计算属性并实现实时数据响应
- 开发场景:基于C# WPF开发GUI应用,采用MVVM架构,用户可通过界面控件调整参数,依赖这些参数的计算模型输出需要在参数调整时实时绘制展示。
- 技术选型:绘图库选用支持MVVM模式的LiveCharts2,示例以简单抛物线函数
y=a*x²为模型,参数a通过界面的TextBox调整,调整后抛物线图形需要同步更新。
MainWindow.xaml 代码
<Window x:Class="TestLvc.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:TestLvc" xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.WPF;assembly=LiveChartsCore.SkiaSharpView.WPF" mc:Ignorable="d" d:DataContext="{d:DesignInstance local:MainViewModel}" Title="MainWindow" Height="600" Width="800"> <Grid> <StackPanel> <Grid Name="DrawingGrid" Width="400" Height="400"> <lvc:CartesianChart Series="{Binding DataPointCalculator.SeriesBindable}" /> </Grid> <TextBox Text="{Binding DataPointCalculator.ParabolaFactor}" Width="30" /> <Button>点击使TextBox失去焦点以更新绑定</Button> <Label Name="LabelBoundValue" Content="{Binding DataPointCalculator.ParabolaFactor}" /> <Label Content="^- 绑定值校验" /> </StackPanel> </Grid> </Window>
界面说明
- 按钮本身无业务逻辑,仅用于让
TextBox失去焦点,触发绑定值更新。 - 按钮下方的
Label与TextBox使用相同绑定路径,用于验证绑定是否正常工作。
MainWindow.xaml.cs 代码
代码中通过三个编译宏切换三种不同实现方案:
use_dependent_property:优先选择的动态属性方案,目前无法正常工作recalculate_points_in_setter:逐点更新集合元素的方案,可正常工作recalculate_property_in_setter:整体替换集合实例的方案,理论可工作但实际无效
using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using LiveChartsCore; using LiveChartsCore.Defaults; using LiveChartsCore.SkiaSharpView; namespace TestLvc; public partial class MainWindow : Window { public MainViewModel vm; public MainWindow() { InitializeComponent(); vm = new MainViewModel(); DataContext = vm; } } public class MainViewModel { public MainViewModel() { DataPointCalculator = new ParabolaCalculator(5); } public ParabolaCalculator DataPointCalculator { get; set; } } public class ParabolaCalculator : INotifyPropertyChanged { private static readonly double[] XValues = Enumerable.Range(-20, 41).Select(stepIndex => (double) stepIndex * 1).ToArray(); private double _parabolaFactor; public ParabolaCalculator(double parabolaFactor = 1) { ParabolaFactor = parabolaFactor; #if use_dependent_property LineSeriesSeries.Values = DataPointsDependent; #elif recalculate_points_in_setter LineSeriesSeries.Values = DataPointsCalculated; #elif recalculate_property_in_setter LineSeriesSeries.Values = DataPointsCalculated; #endif SeriesBindable = new List<ISeries> {LineSeriesSeries}; } public ObservableCollection<ObservablePoint> DataPointsCalculated { get; set; } public ObservableCollection<ObservablePoint> DataPointsDependent => CalculateDataPoints(); public List<ISeries> SeriesBindable { get; set; } public LineSeries<ObservablePoint> LineSeriesSeries { get; set; } = new(); public double ParabolaFactor { get => _parabolaFactor; set { _parabolaFactor = value; OnPropertyChanged(nameof(ParabolaFactor)); #if use_dependent_property // 无需手动计算,数据点为动态计算属性——但如何通知图表底层参数已变更? // 触发PropertyChanged事件无效果 OnPropertyChanged(nameof(SeriesBindable)); OnPropertyChanged(nameof(DataPointsDependent)); OnPropertyChanged(nameof(LineSeriesSeries)); #elif recalculate_points_in_setter RecalculateDataPoints(); #elif recalculate_property_in_setter DataPointsCalculated = CalculateDataPoints(); #endif } } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public void RecalculateDataPoints() { DataPointsCalculated ??= CalculateDataPoints(); foreach (var point in DataPointsCalculated) point.Y = ParabolaFunction((double) point.X); } public ObservableCollection<ObservablePoint> CalculateDataPoints() { ObservableCollection<ObservablePoint> points = new(); foreach (var x in XValues) points.Add(new ObservablePoint(x, ParabolaFunction(x))); return points; } private double ParabolaFunction(double x) { return ParabolaFactor * (x * x); } }
各方案测试问题说明
use_dependent_property 方案问题
该方案是预期的最优实现:数据点完全依赖参数值动态计算,将数据点实现为动态计算属性DataPointsDependent在逻辑上最合理,图表可直接绑定该属性。但实际参数变更时,即使触发对应属性的PropertyChanged事件,图表也不会更新,需要调整实现逻辑才能正常工作。
recalculate_points_in_setter 方案缺陷
该方案下图表绑定到ObservableCollection<ObservablePoint>类型的DataPointsCalculated属性,在参数的setter中遍历更新集合内每个ObservablePoint的Y值,可正常运行。但在多参数、复杂ViewModel场景下维护成本很高,属于手动触发更新,不符合MVVM数据驱动的设计理念。
recalculate_property_in_setter 方案失效问题
该方案下图表同样绑定DataPointsCalculated属性,参数变更时在setter中整体替换DataPointsCalculated集合实例,而非修改集合内元素。按照官方自动更新文档说明,ObservableCollection被替换时LiveCharts应该触发更新,但实际该方案无效。
官方自动更新规则说明:
对绑定到图表的ObservableCollection类型数据源执行添加、删除、替换、清空操作时,LiveCharts会自动更新图表,示例逻辑如下:var valuesCollection = new ObservableCollection(); var lineSeries = new LineSeries(); lineSeries.Values = valuesCollection;注:当前测试使用的LiveCharts2为预发布版本,在LiveCharts 0.x稳定版本上测试也得到了相同的失效结果。
内容的提问来源于stack exchange,提问作者Fred
相关产品推荐
相关产品推荐

