似乎我需要一些帮助来正确同步/绑定ObservableCollection。
背景:在窗口内,应在新数据到达时绘制和更新折线。
使用缓冲区从单独的线程输入新数据。来自缓冲区的数据应附加到ObservalbleCollection,在更改后将其转换为PointCollection(在xaml中绑定)然后绘制。 ObservableCollection实现为自定义类的属性(属性AllSettings的属性pointCollection,实现INotifyPropertyChanged - 未在下面列出)。
这原则上有效,但我认为我有一个锁定问题
我在新数据到达时使用显式锁定,但转换器还需要访问ObservableCollection,我不知道如何锁定转换器内的访问。如何将锁的引用传递给转换器的Convert()方法?
这里有一些代码 - 请注意,我并没有真正知道传入数据,但此时仅用于测试目的的正弦波...
非常需要帮助,甚至更感激; - )
这是xaml中的绑定:
<Window.Resources>
<dc:PointCollectionConverter x:Key="pointCollectionConverter"/>
</Window.Resources>
<Grid x:Name="eegGrid" SizeChanged="gridSizeChanged">
<Canvas x:Name="eegCanvas" ClipToBounds="True" Margin="10,10,10,10" Height="250" Width="1000" Background="Transparent"/>
<Polyline Points="{Binding pointCollection, Converter={StaticResource pointCollectionConverter}, Mode=OneWay}" Stroke="Black" StrokeThickness="2" Name="line" />
</Grid>
“标准”转换器methdos:
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() == typeof(ObservableCollection<Point>) && targetType == typeof(PointCollection))
{
var pointCollection = new PointCollection();
foreach (var point in value as ObservableCollection<Point>)
pointCollection.Add(point);
return pointCollection;
}
return null;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null; //not needed
}
UI类,包括通过缓冲区接收新数据的函数:
public partial class EEGtestDisplay : Window
{
public AllSettings allSettings;
private double xmin = 0;
private double xmax = 6.6;
private double ymin = -1.1;
private double ymax = 1.1;
private object collectionLock = new object();
public EEGtestDisplay(AllSettings allSettings)
{
InitializeComponent();
this.allSettings = allSettings;
this.allSettings.pointCollection = new ObservableCollection<Point>();
BindingOperations.EnableCollectionSynchronization(this.allSettings.pointCollection, collectionLock);
this.DataContext = this.allSettings;
double x, y;
for (float i = 1; i < 50; i++)
{
x = i / 100 * Math.PI;
y = Math.Sin(x);
this.allSettings.pointCollection.Add(NormalizePoint(new Point(x,y)));
}
}
public void PushEEG(BlockingCollection<ILArray<float>> inBuffer)
{
float run = 1;
double i = 50;
double x, y;
foreach (var window in inBuffer.GetConsumingEnumerable())
{
this.allSettings.output = ("Run " + run.ToString() + " - data package received.\n");
x = i/100*Math.PI;
y = Math.Sin(x);
lock (collectionLock)
{
this.allSettings.pointCollection.Add(NormalizePoint(new Point(x, y)));
this.allSettings.pointCollection = this.allSettings.pointCollection;
}
i += 1;
run += 1;
}
}
...}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
这是使用PointCollection而不是ObservableCollection的版本。
XAML - 数据绑定:
<Grid x:Name="eegGrid" SizeChanged="gridSizeChanged">
<Canvas x:Name="eegCanvas" ClipToBounds="True" Margin="10,10,10,10" Height="250" Width="1000" Background="Transparent"/>
<Polyline Name="dataLine" Stroke="Black" Points="{Binding pointList}"/>
</Grid>
C# - 现在使用PointCollection的属性pointList - 实现INotifyPropertyChanged:
public partial class EEGtestDisplay : Window, INotifyPropertyChanged
{
public AllSettings allSettings;
private double xmin = 0;
private double xmax = 6.6;
private double ymin = -1.1;
private double ymax = 1.1;
double position = 50;
private PointCollection _pointList;
public PointCollection pointList
{
get { return this._pointList; }
set
{
this._pointList = value;
NotifyPropertyChanged();
}
}
public EEGtestDisplay(AllSettings allSettings)
{
InitializeComponent();
this.allSettings = allSettings;
this.DataContext = this;
this.pointList = new PointCollection();
double i, x, y;
for (i = 1; i < 50; i++)
{
x = i / 100 * Math.PI;
y = Math.Sin(x);
this.pointList.Add(NormalizePoint(new Point(x, y)));
}
int span = 250;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(span);
timer.Tick += new EventHandler(someEventHandler);
timer.Start();
}
public void PushEEG(BlockingCollection<ILArray<float>> inBuffer)
{
float run = 1;
double i, x, y;
foreach (var window in inBuffer.GetConsumingEnumerable())
{
this.allSettings.output = ("Run " + run.ToString() + " - data package received.\n");
run += 1;
}
}
private Point NormalizePoint(Point pt)
...
private void gridSizeChanged(object sender, SizeChangedEventArgs e)
...
private void someEventHandler(Object sender, EventArgs args)
{
this.pointList.Add(NormalizePoint(new Point(position / 100 * Math.PI, Math.Sin(position / 100 * Math.PI))));
dataLine.Points = this.pointList;
position += 1;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
请注意,我在此处使用DispatcherTimer来强制定期更新,而不是立即使用缓冲区。尽管如此,绑定不会以这种方式工作,因为Polyline不会自动更新,这似乎是一个已知问题(http://connect.microsoft.com/VisualStudio/feedback/details/603544/polyline-with-points-bound-to-pointcollection-doesnt-update-on-pointcollection-changed)。 因此,我“手动”更新dataLine.Points。
但是,如何从我的缓冲区中将新点推送到pointList,它在不同的线程中运行? 如果我将从DispatcherTimer发出的代码移动到PushEEG(),以便它是通过缓冲区(下面)发送的包,我得到“调用线程无法访问此对象,因为不同的线程拥有它”错误消息。我理解为什么会发生这种情况,但不知道如何处理它......我了解到Dispatcher可能有所帮助但却不知道如何在这里应用它......
public void PushEEG(BlockingCollection<ILArray<float>> inBuffer)
{
float run = 1;
double i, x, y;
foreach (var window in inBuffer.GetConsumingEnumerable())
{
this.allSettings.output = ("Run " + run.ToString() + " - data package received.\n");
this.pointList.Add(NormalizePoint(new Point(position / 100 * Math.PI, Math.Sin(position / 100 * Math.PI))));
dataLine.Points = this.pointList;
position += 1;
run += 1;
}
}
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
以下是使用Control-Dispatcher的解决方案:
private delegate void PointChanger(Point point);
public void PushEEG(BlockingCollection<ILArray<float>> inBuffer)
{
float run = 1;
double i, x, y;
foreach (var window in inBuffer.GetConsumingEnumerable())
{
this.allSettings.output = ("Run " + run.ToString() + " - data package received.\n");
x = position / 100 * Math.PI;
y = Math.Sin(x);
Point newPoint = new Point(x,y);
newPoint = NormalizePoint(newPoint);
this.dataLine.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new PointChanger(this.changePoints), newPoint);
position += 1;
run += 1;
}
}
private void changePoints(Point newPoint)
{
this.pointList.Add(newPoint);
this.dataLine.Points = this.pointList;
}