WPF ObservableCollection与转换器的绑定和线程同步

时间:2014-03-12 14:15:11

标签: wpf multithreading xaml converter observablecollection

似乎我需要一些帮助来正确同步/绑定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;
    }

0 个答案:

没有答案