Home > Software design >  WPF/C# : Updating LiveCharts RowSeries with OnPropretyChanged
WPF/C# : Updating LiveCharts RowSeries with OnPropretyChanged

Time:09-26

I have a horizontal bar chart in a ViewModel displaying the top 10 clients in the past 6 or 12 months (default 6). I want the data to switch to 6 or 12 months when the user clicks on the buttons. I just can't seem to figure out how to get the chart to update. I've implemented the INotifyPropertyChanged interface but I must have done it wrong. I've read multiple posts on the subject: still no clue what is wrong.

Screen capture of chart

ReportClientModel

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ReportXpress.Models
{
    public class ReportClientModel : INotifyPropertyChanged
    {
        private string name;
        public string Name 
        {
            get => this.name; 
            set
            {
                this.name = value;
                OnPropertyChanged();
            }
        }
        public double? sales;
        public double? Sales 
        {
            get => this.sales;
            set
            {
                this.sales = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName=null)
        {
            this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ViewModelBase

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;

namespace ReportXpress.ViewModels
{
    // Abstract : can only be used via inheritance
    public abstract class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

ClientViewModel

using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using SkiaSharp;
using System.Collections.ObjectModel;
using System.Drawing;
using LiveChartsCore.Defaults;
using ReportXpress.Models;
using ReportXpress.Repositories;
using Org.BouncyCastle.Tls;
using System.Windows.Input;
using System.ComponentModel;

namespace ReportXpress.ViewModels
{
    public class ClientViewModel : ViewModelBase
    {
        //Fields
        private ObservableCollection<ISeries> clientsSalesSeries;
        public ReportClientModel[] topClients6Months;
        public ReportClientModel[] topClients12Months;

        //Properties
        public ObservableCollection<ISeries> ClientsSalesSeries 
        { get 
            {
                return clientsSalesSeries; 
            } 
            set
            {
                clientsSalesSeries = value;
                OnPropertyChanged(nameof(ClientsSalesSeries));
            }
        }
        public string[] Labels { get; set; }
        public LiveChartsCore.Measure.Margin Margin { get; set; } = new LiveChartsCore.Measure.Margin(25,0,150,50);

        //Events
        public new event PropertyChangedEventHandler PropertyChanged;

        //--> Commands
        public ICommand ShowLast6Months { get; }
        public ICommand ShowLast12Months { get; }
        
        public ClientViewModel()
        {
            //Initialize commands
            ShowLast6Months = new ViewModelCommand(ExecuteShowLast6Months);
            ShowLast12Months = new ViewModelCommand(ExecuteShowLast12Months);

            topClients6Months = new ReportClientModel[]
            {
                new ReportClientModel { Name = "Client 10 name", Sales=30689 },
                new ReportClientModel { Name = "Client 9 name", Sales=31558 },
                new ReportClientModel { Name = "Client 8 name", Sales=38937 },
                new ReportClientModel { Name = "Client 7 name", Sales=42139 },
                new ReportClientModel { Name = "Client 6 name", Sales=49860 },
                new ReportClientModel { Name = "Client 5 name", Sales=51561 },
                new ReportClientModel { Name = "Client 4 name", Sales=69554 },
                new ReportClientModel { Name = "Client 3 name", Sales=70524 },
                new ReportClientModel { Name = "Client 2 name", Sales=276170 },
                new ReportClientModel { Name = "Client 1 name", Sales=345835 }
            };

            topClients12Months = new ReportClientModel[]
{
                new ReportClientModel { Name = "Client 10 name", Sales=56664 },
                new ReportClientModel { Name = "Client 9 name", Sales=79135 },
                new ReportClientModel { Name = "Client 8 name", Sales=80280 },
                new ReportClientModel { Name = "Client 7 name", Sales=83675 },
                new ReportClientModel { Name = "Client 6 name", Sales=92612 },
                new ReportClientModel { Name = "Client 5 name", Sales=126429 },
                new ReportClientModel { Name = "Client 4 name", Sales=158313 },
                new ReportClientModel { Name = "Client 3 name", Sales=163430 },
                new ReportClientModel { Name = "Client 2 name", Sales=442796 },
                new ReportClientModel { Name = "Client 1 name", Sales=592028 }
};
            //Default values : last 6 months
            ExecuteShowLast6Months(null);

            Formatter = value => value.ToString("0")   " k$";
        }

        protected virtual new void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void ExecuteShowLast12Months(object obj)
        {
            ClientsSalesSeries = new ObservableCollection<ISeries>
            {
                new RowSeries<ReportClientModel>
                {
                    Values = topClients12Months,
                    Mapping = (client, point) =>
                    {
                        // use the Sales property in this series
                        point.PrimaryValue = (float)client.Sales;
                        // use the Index of the item
                        point.SecondaryValue = point.Context.Entity.EntityIndex;
                    },
                    Fill = new SolidColorPaint(SKColors.SkyBlue),
                    TooltipLabelFormatter = (point) => point.PrimaryValue.ToString("C2"),
                    DataLabelsPaint = new SolidColorPaint(SKColors.White),
                    DataLabelsSize = 14,
                    DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.End,
                    DataLabelsFormatter = (point) => topClients6Months[point.Context.Entity.EntityIndex].Name,
                }
            };
        }

        private void ExecuteShowLast6Months(object obj)
        {
            ClientsSalesSeries = new ObservableCollection<ISeries>
            {
                new RowSeries<ReportClientModel>
                {
                    Values = topClients6Months,
                    Mapping = (client, point) =>
                    {
                        // use the Sales property in this series
                        point.PrimaryValue = (float)client.Sales;
                        // use the Index of the item
                        point.SecondaryValue = point.Context.Entity.EntityIndex;
                    },
                    Fill = new SolidColorPaint(SKColors.SkyBlue),
                    TooltipLabelFormatter = (point) => point.PrimaryValue.ToString("C2"),
                    DataLabelsPaint = new SolidColorPaint(SKColors.White),
                    DataLabelsSize = 14,
                    DataLabelsPosition = LiveChartsCore.Measure.DataLabelsPosition.End,
                    DataLabelsFormatter = (point) => topClients6Months[point.Context.Entity.EntityIndex].Name,
                }
            };
        }

        public Axis[] XAxesAmounts { get; set; } =
        {
            new Axis
            {
                LabelsPaint = new SolidColorPaint{ Color= SKColors.White},
                Labeler = Labelers.Currency,
                NameTextSize=10,
                ShowSeparatorLines=true,
                SeparatorsPaint = new SolidColorPaint { Color = SKColor.Parse("#33ffffff")},
                TextSize=10
            }
        };

        public Axis[] YAxesClients { get; set; } =
        {
            new Axis
            {
                Name = "",
                NamePaint = new SolidColorPaint{ Color=SKColors.White},
                LabelsPaint = new SolidColorPaint{ Color= SKColors.White},
                Labeler = null,
                IsVisible = true,
                NameTextSize=10,
                LabelsRotation=0,
                ShowSeparatorLines = false,
                MinLimit=-0.5,
                TextSize=10
            }

        };
    }
}

ClientView (chart section only)

                <!--Chart for Top Clients-->
                <lvc:CartesianChart Grid.Row="1" Margin="0,0,0,15"
                                    Name="chartClientSales"
                                    Series="{Binding ClientsSalesSeries}"
                                    XAxes="{Binding XAxesAmounts}"
                                    YAxes="{Binding YAxesClients}"
                                    DrawMargin="{Binding Margin}"
                                    TooltipPosition="Right"
                                    ZoomMode="Both"
                                    />

Adding a breakpoint in ExecuteShowLast12Months confirms the data in the series is changed, but the chart is not updated to reflect the new data. What am I doing wrong? I haven't seen any other posts that use a custom class in a RowSeries.

CodePudding user response:

You have two options

  1. Update the series by clearing the ObservableCollection and adding the new data to it directly (this way you don't have to implement INotifyPropertyChanged)
public ObservableCollection<ISeries> ClientsSalesSeries {set; get;} = new();
// ...
private void ExecuteShowLast12Months(object obj)
{
    ClientsSalesSeries.Clear();
    ClientsSalesSeries.Add(new RowSeries<ReportClientModel>{ });
}
// ...
private void ExecuteShowLast6Months(object obj)
{
    ClientsSalesSeries.Clear();
    ClientsSalesSeries.Add(new RowSeries<ReportClientModel>{ });
}
  1. Set binding mode of Series to TwoWay (you can use any collection, ex. List)
Series="{Binding ClientsSalesSeries, Mode=TwoWay}"

The first option is preferred if you want to take benefit of ObservableCollection.

  • Related