I'm populating a collection of data points and showing it on a line chart. A user control having IEnumerable dependency property which is bind to ObservableCollection. I'm using LiveCharts for plotting data points.
Issue: The CollectionChanged is not getting fired when I add data to the collection in VM_CaptureData.
Please help me with this issue. Thanks in advance. Please find the sample project
here.
Here is my code,
What I have tried:
UserControl.xaml
<UserControl x:Class="ChartLibrary.LineChart"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<lvc:CartesianChart x:Name="chart" Series="{Binding Series_Collection}" LegendLocation="Bottom"
AnimationsSpeed="0:0:0.5" Hoverable="False" DataTooltip="{x:Null}" >
<lvc:CartesianChart.AxisY>
<lvc:Axis Name="Axis" Title="Readings" LabelFormatter="{Binding AxisYFormatter}" />
</lvc:CartesianChart.AxisY>
<lvc:CartesianChart.AxisX>
<lvc:Axis Title="Data Points" Labels="{Binding XAxisLabel}" LabelsRotation="-87">
<lvc:Axis.Separator>
<lvc:Separator StrokeThickness="1" StrokeDashArray="2" IsEnabled="True" Step="1">
<!--Here, see the IsEnabled => false-->
<lvc:Separator.Stroke>
<SolidColorBrush Color="#404F56" />
</lvc:Separator.Stroke>
</lvc:Separator>
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
</Grid>
UserControl.cs
public partial class LineChart : UserControl
{
public IEnumerable<double> Values
{
get { return (IEnumerable<double>)GetValue(ValuesProperty); }
set { SetValue(ValuesProperty, value); }
}
public static readonly DependencyProperty ValuesProperty =
DependencyProperty.Register("Values", typeof(IEnumerable<double>), typeof(LineChart), new PropertyMetadata(null, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d != null && d is LineChart)
{
LineChart lineChart = d as LineChart;
lineChart.ValueChanged(e);
}
}
public LineChartData ChartData { get; set; }
public LineChart()
{
InitializeComponent();
Values = new ObservableCollection<double>();
ChartData = new LineChartData();
chart.DataContext = ChartData;
Unloaded += LineChart_Unloaded;
}
private void LineChart_Unloaded(object sender, RoutedEventArgs e)
{
ChartData = null;
chart.DataContext = null;
}
private void Values_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null && e.NewItems.Count > 0)
{
if (e.NewItems.Count == 1)
ChartData.AddDataPoint(Convert.ToDouble(e.NewItems[0]));
else
ChartData.AddDataPointrange(e.NewItems.Cast<double>().GetEnumerator() as IEnumerable<double>);
}
}
public void ValueChanged(DependencyPropertyChangedEventArgs e)
{
if (ChartData == null) return;
if (e.NewValue == null) return;
try
{
switch (e.Property.Name)
{
case "Values":
if (e.OldValue != null)
{
var oldCollection = e.OldValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= Values_CollectionChanged;
}
}
var newCollection = e.NewValue as INotifyCollectionChanged;
if (newCollection != null)
{
newCollection.CollectionChanged += Values_CollectionChanged;
if (e.NewValue is ObservableCollection<double>)
{
ChartData.Clear();
ChartData.AddDataPointrange(e.NewValue as ObservableCollection<double>);
}
}
break;
default:
break;
}
}
catch (Exception ex)
{
throw ex;
}
}
}
MainWindow.xaml
<Window x:Class="WpfTestDP_Coll_Changed.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:WpfTestDP_Coll_Changed"
xmlns:layouts="clr-namespace:WpfTestDP_Coll_Changed.Layouts"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter"/>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="0,0,0,10">
<Button Name="btnStart" Content="Start" Command="{Binding CaptureDataCommand}" CommandParameter="DemoTimerStart" Margin="0,0,10,0"/>
<Button Name="btnStop" Content="Stop" Command="{Binding CaptureDataCommand}" CommandParameter="DemoTimerStop" />
</StackPanel>
<Grid>
<layouts:ChartLayout2 Visibility="{Binding Path=IsChecked,ElementName=radLayout2, Converter={StaticResource VisibleIfTrueConverter}}"/>
</Grid>
</DockPanel>
MainWindow.cs
public partial class MainWindow : Window
{
VM_CaptureData VM_CaptureData;
public MainWindow()
{
InitializeComponent();
Unloaded += CaptureDataControl_Unloaded;
VM_CaptureData = new VM_CaptureData();
DataContext = VM_CaptureData;
}
private void CaptureDataControl_Unloaded(object sender, RoutedEventArgs e)
{
DataContext = null;
VM_CaptureData.Deinitialize();
VM_CaptureData = null;
}
}
ViewModel for MainWindow
class VM_CaptureData : INotifyPropertyChanged
{
private ObservableCollection<ChartLibrary.ChartDataPopulation> parametersData = new ObservableCollection<ChartLibrary.ChartDataPopulation>();
public ObservableCollection<ChartLibrary.ChartDataPopulation> ParametersData
{
get { return parametersData; }
set { parametersData = value; OnPropertyChanged(); }
}
private ChartLibrary.ChartDataPopulation selectedParameterData;
public ChartLibrary.ChartDataPopulation SelectedParameterData
{
get { return selectedParameterData; }
set { selectedParameterData = value; OnPropertyChanged(); }
}
DispatcherTimer dispatcherTimer;
public RelayCommand<object> CaptureDataCommand { get; set; }
public VM_CaptureData()
{
CaptureDataCommand = new RelayCommand<object>(Execute, CanExecute);
ParametersData.Add(new ChartLibrary.ChartDataPopulation());
ParametersData.Add(new ChartLibrary.ChartDataPopulation());
ParametersData.Add(new ChartLibrary.ChartDataPopulation());
selectedParameterData = ParametersData.First();
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Interval = TimeSpan.FromMilliseconds(1000);
dispatcherTimer.Tick += DispatcherTimer_Tick;
}
Random random = new Random();
private void DispatcherTimer_Tick(object sender, EventArgs e)
{
foreach (var item in ParametersData)
{
item.AddDataAndCalculate(random.NextDouble());
}
}
private bool CanExecute(object arg)
{
return SelectedParameterData != null;
}
private void Execute(object obj)
{
try
{
if (obj != null)
{
switch (obj.ToString())
{
case "DemoTimerStart": dispatcherTimer.Start(); break;
case "DemoTimerStop": dispatcherTimer.Stop(); break;
default:
break;
}
}
}
catch (Exception ex)
{
throw ex;
}
}
internal void Deinitialize()
{
try
{
dispatcherTimer.Stop();
ParametersData.Clear();
SelectedParameterData = null;
}
catch (Exception ex)
{
throw ex;
}
}
#region INotifyPropertyChangedImplementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (PropertyChanged != null) PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}