221 lines
4.5 KiB
C#
221 lines
4.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.Windows;
|
|
using System.Windows.Threading;
|
|
|
|
namespace Microsoft.Research.DynamicDataDisplay.DataSources
|
|
{
|
|
// todo I don't think that we should create data source which supports
|
|
// suspending its DataChanged event - it is better to create
|
|
// collection with the same functionality - then it would be able to be used
|
|
// as a source in many data sources.
|
|
public class ObservableDataSource<T> : IPointDataSource
|
|
{
|
|
|
|
/// <summary>True if collection was changed between SuspendUpdate and ResumeUpdate
|
|
/// or false otherwise</summary>
|
|
private bool collectionChanged = false;
|
|
|
|
/// <summary>True if event should be raised on each collection change
|
|
/// or false otherwise</summary>
|
|
private bool updatesEnabled = true;
|
|
|
|
public ObservableDataSource()
|
|
{
|
|
collection.CollectionChanged += OnCollectionChanged;
|
|
|
|
// todo this is hack
|
|
if (typeof(T) == typeof(Point))
|
|
{
|
|
xyMapping = t => (Point)(object)t;
|
|
}
|
|
}
|
|
|
|
public ObservableDataSource(IEnumerable<T> data)
|
|
: this()
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
foreach (T item in data)
|
|
{
|
|
collection.Add(item);
|
|
}
|
|
}
|
|
|
|
public void SuspendUpdate()
|
|
{
|
|
updatesEnabled = false;
|
|
}
|
|
|
|
public void ResumeUpdate()
|
|
{
|
|
updatesEnabled = true;
|
|
if (collectionChanged)
|
|
{
|
|
collectionChanged = false;
|
|
RaiseDataChanged();
|
|
}
|
|
}
|
|
|
|
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (updatesEnabled)
|
|
{
|
|
RaiseDataChanged();
|
|
}
|
|
else
|
|
{
|
|
collectionChanged = true;
|
|
}
|
|
}
|
|
|
|
private readonly ObservableCollection<T> collection = new ObservableCollection<T>();
|
|
|
|
public ObservableCollection<T> Collection
|
|
{
|
|
get { return collection; }
|
|
}
|
|
|
|
public void AppendMany(IEnumerable<T> data)
|
|
{
|
|
if (data == null)
|
|
throw new ArgumentNullException("data");
|
|
|
|
updatesEnabled = false;
|
|
foreach (var p in data)
|
|
{
|
|
collection.Add(p);
|
|
}
|
|
updatesEnabled = true;
|
|
RaiseDataChanged();
|
|
}
|
|
|
|
public void AppendAsync(Dispatcher dispatcher, T item)
|
|
{
|
|
dispatcher.Invoke(DispatcherPriority.Normal,
|
|
new Action(() =>
|
|
{
|
|
collection.Add(item);
|
|
RaiseDataChanged();
|
|
}));
|
|
}
|
|
|
|
private readonly List<Mapping<T>> mappings = new List<Mapping<T>>();
|
|
private Func<T, double> xMapping;
|
|
private Func<T, double> yMapping;
|
|
private Func<T, Point> xyMapping;
|
|
|
|
public void SetXMapping(Func<T, double> mapping)
|
|
{
|
|
if (mapping == null)
|
|
throw new ArgumentNullException("mapping");
|
|
|
|
this.xMapping = mapping;
|
|
}
|
|
|
|
public void SetYMapping(Func<T, double> mapping)
|
|
{
|
|
if (mapping == null)
|
|
throw new ArgumentNullException("mapping");
|
|
|
|
this.yMapping = mapping;
|
|
}
|
|
|
|
public void SetXYMapping(Func<T, Point> mapping)
|
|
{
|
|
if (mapping == null)
|
|
throw new ArgumentNullException("mapping");
|
|
|
|
this.xyMapping = mapping;
|
|
}
|
|
|
|
#region IChartDataSource Members
|
|
|
|
private class ObservableIterator : IPointEnumerator
|
|
{
|
|
private readonly ObservableDataSource<T> dataSource;
|
|
private readonly IEnumerator<T> enumerator;
|
|
|
|
public ObservableIterator(ObservableDataSource<T> dataSource)
|
|
{
|
|
this.dataSource = dataSource;
|
|
enumerator = dataSource.collection.GetEnumerator();
|
|
}
|
|
|
|
#region IChartPointEnumerator Members
|
|
|
|
public bool MoveNext()
|
|
{
|
|
return enumerator.MoveNext();
|
|
}
|
|
|
|
public void GetCurrent(ref Point p)
|
|
{
|
|
dataSource.FillPoint(enumerator.Current, ref p);
|
|
}
|
|
|
|
public void ApplyMappings(DependencyObject target)
|
|
{
|
|
dataSource.ApplyMappings(target, enumerator.Current);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
enumerator.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
private void FillPoint(T elem, ref Point point)
|
|
{
|
|
if (xyMapping != null)
|
|
{
|
|
point = xyMapping(elem);
|
|
}
|
|
else
|
|
{
|
|
if (xMapping != null)
|
|
{
|
|
point.X = xMapping(elem);
|
|
}
|
|
if (yMapping != null)
|
|
{
|
|
point.Y = yMapping(elem);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyMappings(DependencyObject target, T elem)
|
|
{
|
|
if (target != null)
|
|
{
|
|
foreach (var mapping in mappings)
|
|
{
|
|
target.SetValue(mapping.Property, mapping.F(elem));
|
|
}
|
|
}
|
|
}
|
|
|
|
public IPointEnumerator GetEnumerator(DependencyObject context)
|
|
{
|
|
return new ObservableIterator(this);
|
|
}
|
|
|
|
public event EventHandler DataChanged;
|
|
private void RaiseDataChanged()
|
|
{
|
|
if (DataChanged != null)
|
|
{
|
|
DataChanged(this, EventArgs.Empty);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|