7


first of all I describe my objective I want to achive. I want to visualise a continuous data stream (maximum 1000 values per second but could be reduced). This data stream should be visualised as a chart - being more precise it's a visualisation of an ECG among other things. My first idea was using polyline and bind it to a point collection. The problem here is that nothing is shown on the UI. Perhaps it's a wrong aproach for this task. Better ideas are welcomed. Here ist my code so far. First the View:

 
<Canvas>
  <Polyline Points="{Binding Points}" Stroke="Red" StrokeThickness="2" />
</Canvas>

For the sake of simplicity I use the code-behind even though I use the MVVM-pattern. That's also the reason why I want to use the binding and not just the name of the polyline and add the values.


public partial class MainWindow : Window
{
   private short[] data = new short[]{ 10,30,50,70,90,110,130,150,170,190,210 };
   private short[] data1 = new short[] { 15,14,16,13,17,12,18,11,19,10,24 };

    public MainWindow()
    {
        InitializeComponent();
        for (int i = 0; i < data.Length; i++)
        {
            Points.Add(new Point(data[i], data1[i]));
        }
    }

    private PointCollection _points = new PointCollection();
    public PointCollection Points
    {
        get { return _points; }
    }

}

I know that is no good coding style but for first tests its enough for me. I use array data for x-values and data1 for y-values. Can anyone tell me whats wrong with that binding? What's to be done for a continuous update of the view, whenever new values occur?
Thanks for your help in advance.

[Updated new version] The view:


<Window.Resources>
        <my:PointCollectionConverter x:Key="myPointsConverter"/>
</Window.Resources>
    <Grid Name="grid">
        <Polyline x:Name="ekglineI" Points="{Binding Points, Converter={StaticResource myPointsConverter}}" Stroke="Red" StrokeThickness="2"  />
        <Button Content="Button" Click="button1_Click" />
</Grid>
The code-behind which draws a polyline on startup and later on when a button is clicked.

public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private short[] data = new short[] { 10, 30, 50, 70, 90, 110, 130, 150, 170, 190, 210 };
        private short[] data2 = new short[] { 230, 250, 270, 290, 300, 310, 330, 350, 370, 390, 410 };
        private short[] data1 = new short[] { 15, 14, 16, 13, 17, 12, 18, 11, 19, 10, 24 };

public MainWindow() { InitializeComponent(); grid.DataContext = this; for (int i = 0; i < data.Length; i++) { Points.Add(new Point(data[i], data1[i])); } } public event PropertyChangedEventHandler PropertyChanged; private ObservableCollection _points = new ObservableCollection(); public ObservableCollection Points { get { return _points; } }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < data2.Length; i++)
        {
            Points.Add(new Point(data2[i], data1[i]));
        }
        PropertyChanged(this, new PropertyChangedEventArgs("Points"));
    }

Now what I want to do is getting rid of this line: grid.DataContext = this; so that I can use my MVVM or is there another possibility?

Kai Krupka
  • 137
  • 1
  • 2
  • 8

3 Answers3

10

In order to bind the Polyline Points attribute to your viewmodel successfully (i.e. to have it update when the bound PointCollection changes), you should avoid changing the PointCollection as a collection (Clear, Add, etc). The Polyline will not notice that, even binding to an ObservableCollection of Points with a custom converter will not help.

Instead, you should consider your PointCollection as a property: set it with a newly created PointCollection, and fire a NotifyPropertyChanged event:

    private PointCollection points = new PointCollection();
    public PointCollection Points 
    {
        get { return points; }
        set
        {
            points = value;
            NotifyPropertyChanged("Points");
        }
    }

    public void SomeUpdateFunc() 
    {
        PointCollection pc = new PointCollection();

        // Do some adding: pc.Add(new Point(x, y)); etc

        this.Points = pc; // set via the setter, so the notification will fire
    }

Now the Polyline should be updated properly, good luck!

Marco Hansma
  • 151
  • 1
  • 5
2

There is at least one possible way to remove grid.DataContext = this;

Add Binding to RelativeSource to the grid itself. In this case the xaml file will looks like

<Window x:Class="WpfApplication2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:WpfApplication2">
<Grid Name="grid" DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=my:MainWindow, AncestorLevel=1}}">
    <Canvas>
        <Polyline Points="{Binding Points}" Stroke="Red" StrokeThickness="2" />
    </Canvas>
</Grid>

And the code behind will be like this

 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Text;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Navigation;
 using System.Windows.Shapes;
 using System.ComponentModel;

 namespace WpfApplication2
 {
    public partial class MainWindow : Window , INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();

            for (int i = 0; i < data.Length; i++)
            {
                 Points.Add(new Point(data[i], data1[i]));
            }
            NotifyPropertyChanged("Points");                
        }

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        public PointCollection Points { get { return _points; } }

        public event PropertyChangedEventHandler PropertyChanged;
        private PointCollection _points = new PointCollection();
        private short[] data = new short[] { 10, 30, 50, 70, 90, 110, 130, 150, 170, 190, 210 };
        private short[] data1 = new short[] { 15, 14, 16, 13, 17, 12, 18, 11, 19, 10, 24 };


    }
}
Mif
  • 632
  • 7
  • 7
2

Kai to make the change notification propagate to your bindings you should be making use of a collection which implements change notificaiton, PointCollection does not do this. You could create your own collection however I'd recommend making use of ObservableCollection<T>.

In addition here is a similar SO post which also touches on a few other options for making the UI aware of your changes.

Community
  • 1
  • 1
Aaron McIver
  • 24,527
  • 5
  • 59
  • 88
  • Ok, I also tried this approach. Is it necessary to use a ValueConverter for my ObservableCollection? Because after changing PointCollection to ObservableCollection the application runs but nothing gets visible although there are the values in the collection. – Kai Krupka Oct 18 '10 at 15:33
  • If you are using the Points property you should not need to as it is a DP. Make sure there are no errors in the output window as the pathing of your binding may be off. If you are passing the ObservableCollection into the DataContext it will simply be Points="{Binding}" – Aaron McIver Oct 18 '10 at 15:42
  • if I say Points={Binding Points} there is no problem in the output window. But when I say this.DataContext=Points; in the code-behind and Points="{Binding}" in the view I get an error. – Kai Krupka Oct 18 '10 at 15:51
  • System.Windows.Data Error: 1 : Cannot create default converter to perform 'one-way' conversions between types 'System.Collections.ObjectModel.ObservableCollection`1[System.Windows.Point]' and 'System.Windows.Media.PointCollection'. Consider using Converter property of Binding. BindingExpression:Path=; DataItem='ObservableCollection`1' (HashCode=45943265); target element is 'Polyline' (Name='pline'); target property is 'Points' (type 'PointCollection') – Kai Krupka Oct 18 '10 at 15:53
  • What about the INotifyCollectionChanged? Should I implement this one, too? – Kai Krupka Oct 18 '10 at 15:57
  • Add a ValueConverter and convert the ObservableCollection to a PointCollection. Or create a custom PointCollection which implements INotifyCollectionChanged. You can do the first one for testing, however the custom collection would be the right way to go IMHO. – Aaron McIver Oct 18 '10 at 16:13
  • ok great, with ValueConverter and converting a ObservableCollection to a PointCollection works. Problem now is that I pass it to the DataContext but for using this in my MVVM pattern and therefore I can't do it that way, right? For that I should use a DP, but then a error occurs during registration of the DP because the propertyType isn't a ObservableCollection, it's a PointCollection. How can I solve this problem? Is there another solution than a DP? – Kai Krupka Oct 19 '10 at 08:34
  • Making use of the DataContext is aligned with the MVVM pattern. How you go about pushing that data to the DataContext may not be in line however. You keep making references to a DP, however you do not have to have a DP on your ViewModel. If you simply keep the ObservableCollection as is, use your converter to swap it to a PointCollection or create your own PointCollection which implements INotifyCollectionChanged, then set the DataContext in the View via constructor injection ala Unity you will be rightfully aligned with the MVVM pattern in its entirety. – Aaron McIver Oct 19 '10 at 14:25
  • If you do not care about a pure MVVM approach, get a reference to your view, set the DataContext of your View using your ViewModel with the property containing the Points and all should be good. – Aaron McIver Oct 19 '10 at 14:26
  • Now it works completely with Mvvm. I don't know why it works now but it does. Because I already used the Unity Framework and my codebehind has one additional property: [Dependency] public ViewModel VM { set { _viewmodel = value; this.DataContext = _viewmodel; } } THANKS for your help!!! – Kai Krupka Oct 20 '10 at 14:42
  • Hey Aaron,you talked about a custom PointCpollection which implements INotifyCollectionChange. There is one problem which I don't understand. How is it possible to implement a custom PointCollection if i can't inherit from PointCollection because it's a sealed class? – Kai Krupka Oct 21 '10 at 13:46
  • @Kai Wasn't aware it was sealed, checked out these links, Bea can explain it much better then I can, gives you a few options to attack the problem, one of which uses our previous converter, http://bea.stollnitz.com/blog/?p=35 http://bea.stollnitz.com/blog/?p=36 http://bea.stollnitz.com/blog/?p=37 – Aaron McIver Oct 21 '10 at 14:48
  • this solutions looks great, but there is again a drawback to this. The problem is that I work with Tasks. Therefore I have to synchronize the UI-thread with the background thread. Normally I do this with the Dispatcher (passed via constructor injection to the necessary classes). But in this case my only option is to get my CurrentDispatcher in my Converter class. This works until a "System.Reflection.TargetParameterCountException" occurs and I have no clue where the error lies. – Kai Krupka Oct 22 '10 at 15:06
  • Are you using the BackgroundWorker? It'll marshal back to the UI thread for you...and encapsulates the logic nicely. It might be worth revisiting your threading model as you want it performing the complex processing or in the case of a web services call or file I/O when you do not know how long it is going to take. It sounds like the background thread and the UI are tightly coupled. – Aaron McIver Oct 22 '10 at 15:10
  • The BackgroundWorker Class no. Not yet. But I'm not sure if I understand what you mean. But I explain my setting: – Kai Krupka Oct 22 '10 at 16:05
  • Every time I get a new data package multiple polylines get updated. For every polylien I call a kind of drawing-method which handles my new data and push it to the observable collection. Every Draw-Method is a seperate Task. If I'm right I have to synchronize the changed collections with the dispatcher?! The problem ist now that I don't get the UI thread and the other thread synchronized – Kai Krupka Oct 22 '10 at 16:11
  • Use a background worker to spin that off...then you can report progress, etc...once complete draw right on the UI thread with your newly received data. – Aaron McIver Oct 22 '10 at 16:14
  • After some time everythingworks. Now I use the converter by Bea and the dispatcher asyn. The Backgroundworker has pros and cons in my situation but my decision was the dispatcher. But I think the converter by Bea has one disadvantage. The dictionary grows and grows and consequently my memory and cpu usage. now assume every second 4 dictionary entries were made and hold. After one minute there are already 240 entries and for every entry there are 800 points in my case. Do you have an idea for that problem? I think the problem lies in the fact that I replace points in my observable collection. – Kai Krupka Oct 27 '10 at 08:53
  • But my observable collection shouldn't be longer than a predefined size and after that values are replaced from the beginning on. – Kai Krupka Oct 27 '10 at 08:54
  • Kai you could create your custom observable collection, then override the add method, when the limit is reached, you can start removing then replacing the items as you need... – Aaron McIver Oct 27 '10 at 14:19