I would like to re-render my WPF chart control when data source is changed. My minimum requirement for a data source is IEnumerable<Point> but it could be also ObservableCollection<Point> or BindingList<Point>, so collection change notifications are sometimes becoming available.
Chart Serie class (simplified):
class Serie : Freezable
{
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(IEnumerable<Point>), typeof(Serie));
public Serie()
{
new ChangeListner(this, DataProperty);
}
public IEnumerable<Point> Data
{
get { return (IEnumerable<Point>)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public void Draw(DrawingContext drawingContext)
{
if (Data == null)
return;
foreach (var point in Data)
drawingContext.DrawEllipse(Brushes.Black, null, point, 4, 4);
}
protected override Freezable CreateInstanceCore() => new Serie();
}
Where notifications are being monitored by (real code):
class ChangeListner
{
public ChangeListner(Freezable subject, DependencyProperty property)
{
Subject = subject;
Property = property;
DependencyPropertyDescriptor
.FromProperty(property, subject.GetType())
.AddValueChanged(subject, OnChanged);
}
private void OnChanged(object sender, EventArgs e)
{
var value = Subject.GetValue(Property);
BindingList = value as IBindingList;
NotifyCollectionChanged = value as INotifyCollectionChanged;
NotifyPropertyChanged = value as INotifyPropertyChanged;
}
Freezable Subject { get; }
DependencyProperty Property { get; }
INotifyPropertyChanged _notifyPropertyChanged;
INotifyPropertyChanged NotifyPropertyChanged
{
set
{
if (_notifyPropertyChanged != null)
_notifyPropertyChanged.PropertyChanged -= _notifyPropertyChanged_CollectionChanged;
_notifyPropertyChanged = value;
if (_notifyPropertyChanged != null)
_notifyPropertyChanged.PropertyChanged += _notifyPropertyChanged_CollectionChanged;
}
}
void _notifyPropertyChanged_CollectionChanged(object sender, PropertyChangedEventArgs e) =>
Notify();
INotifyCollectionChanged _notifyCollectionChanged;
INotifyCollectionChanged NotifyCollectionChanged
{
set
{
if (_notifyCollectionChanged != null)
_notifyCollectionChanged.CollectionChanged -= _notifyCollectionChanged_CollectionChanged;
_notifyCollectionChanged = value;
if (_notifyCollectionChanged != null)
_notifyCollectionChanged.CollectionChanged += _notifyCollectionChanged_CollectionChanged;
}
}
void _notifyCollectionChanged_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) =>
Notify();
IBindingList _bindingList;
IBindingList BindingList
{
set
{
if (_bindingList != null)
_bindingList.ListChanged -= _bindingList_ListChanged;
_bindingList = value;
if (_bindingList != null)
_bindingList.ListChanged += _bindingList_ListChanged;
}
}
void _bindingList_ListChanged(object sender, ListChangedEventArgs e) =>
Notify();
void Notify() => Subject
.GetType()
.GetMethod("NotifySubPropertyChange", BindingFlags.Instance | BindingFlags.NonPublic)
.Invoke(Subject, new[] { Property });
}
I am not quite sure about this Notify method – reflection invocation is slow and dangerous. Is there a better way to make Freezable pickup changes?
Here is the custom control code I use for testing:
class Plot : Control
{
public static readonly DependencyProperty SeriesProperty =
DependencyProperty.Register("Series", typeof(FreezableCollection<Serie>), typeof(Plot),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
public Plot()
{
Series = new FreezableCollection<Serie>();
}
public FreezableCollection<Serie> Series
{
get { return (FreezableCollection<Serie>)GetValue(SeriesProperty); }
set { SetValue(SeriesProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (Series == null)
return;
foreach (var serie in Series)
serie.Draw(drawingContext);
}
}