How to data bind a Polygon’s Points to a data source – Part I

Today I will talk about one solution for data binding the Points property of a Polygon to a source collection.
The Polygon’s Points property is of type System.Windows.Media.PointCollection, which can be found in Avalon’s PresentationCore.dll. The first approach I’ve seen most people take when they want to bind the Points property to some data is to add a property of type PointCollection to their source, and bind to it. This works in the sense that the Polygon will display the Points it is binding to, but there are a couple of problems with this approach:
- If we change the collection of points in the data source, the change will not be reflected in the UI. This can be fixed by making the collection of points in the source a DependencyProperty.
- Adding a PointCollection to the data source introduces a dependence on an Avalon type that brings in several UI concepts (dependency properties and freezable objects). Ideally, we should strive for no coupling between the presentation technology and the data source. I’m sure those of you who are porting applications from WinForms to Avalon understand the reasons why this is good practice: If your data layer is independent from the presentation layer, it will be easier to migrate to the next UI technology, or, more commonly, to present the same data in multiple ways within the same application.
With these issues in mind, the first step is to figure out how to store the collection of points in a way that is independent of presentation-related Avalon code. Every time I need to data bind to a collection, I think of ObservableCollection<T> because it provides collection change notifications. This would be my collection of choice if I were binding an ItemsControl to the points, but this scenario is a little different.
Before I go any further, let me explain the two types of change notifications we support in data binding:
Property change notifications
- Your data source needs to implement INotifyPropertyChanged and raise the PropertyChanged event, passing the appropriate property name in the event arguments, whenever a property changes. If you pass null or the empty string as the property name, the binding engine assumes that all properties of the source have changed. If you have a property in your source that is a collection, you can still benefit from property change notifications: you may need to notify the binding engine when the whole collection is being replaced by a different one. In other words, you may need to notify that the property that holds the collection has changed.
Collection change notifications
- Your data source needs to implement INotifyCollectionChanged. Typically, when you need collection change notifications, you can simply use an ObservableCollection<T> which does all the hard work of implementing this interface for you. I haven’t encountered a scenario where a customer needed collection change notifications but couldn’t use ObservableCollection<T> for some reason.This interface notifies the UI when there is some change in the collection, such as when an item is added or removed. If your data source is a business object, the binding engine is smart enough to add or remove only the UI items that are needed, rather than regenerating the entire collection of UI items. If your source is XML or an IBindingList, we are as clever as we can be with the information that we are given by those APIs.The item generator associated with an ItemsControl listens to these collection change notifications and is responsible for determining the corresponding changes that will be done to elements in the UI.In this scenario, I am binding a single DP (Polygon’s Points) to a collection. Since there is no ItemsControl involved, even if I provide collection change notifications in the data source, there is no item generator listening to those notifications. Now that I’ve determined that I don’t need all the complexity of ObservableCollection<T>, I can go ahead and pick a simpler collection. I decided to pick List<T>.
But now I have a problem: if there is no one listening to collection changes in the UI, how can I automatically update my UI when I make a change to the source collection? The solution to this problem is to raise a property change notification when the collection changes. This may seem a little strange at first: there is a collection in the scenario, but there is no item generation in the UI, which makes this scenario somewhat unusual. But if you think about it, raising a property changed notification invalidates the Points property and causes the Polygon to re-render, which is what we want. This is how I implemented my data source:
public class PolygonItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private List<Point> points = new List<Point>();
public ReadOnlyCollection<Point> Points
{
get { return new ReadOnlyCollection<Point>(points); }
set
{
points = new List<Point>(value);
OnPropertyChanged("Points");
}
}
public void Subdivide()
{
int count = this.points.Count;
List<Point> newPoints = new List<Point>(count * 2);
for (int i = 0; i < count; i++)
{
Point previousPoint = this.points[i];
Point nextPoint = this.points[(i + 1) % count];
newPoints.Add(previousPoint);
Vector offset = nextPoint - previousPoint;
Vector normal = new Vector(offset.Y, -offset.X);
newPoints.Add(previousPoint + 0.5 * offset + 0.4 * normal);
}
this.points = newPoints;
OnPropertyChanged("Points");
}
public PolygonItem()
{
points.Add(new Point(275, 100));
points.Add(new Point(375, 200));
points.Add(new Point(275, 300));
points.Add(new Point(175, 200));
}
}
I decided to change the type of the public Points property to a ReadOnlyCollection<T> to prevent the user from making changes directly to the collection returned by the Points property, which would bypass change notifications.
At this point I have a source collection of type ReadOnlyCollection<T> and a target collection of type PointCollection. Because I have incompatible types and there is no default Converter between the two, I need to implement my own. Here is my code:
public class Converter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
IEnumerable<Point> enumerable = value as IEnumerable<Point>;
PointCollection points = null;
if (enumerable != null)
{
points = new PointCollection(enumerable);
}
return points;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException("ConvertBack should never be called");
}
}
The UI is pretty straightforward:
<Window.Resources>
<local:PolygonItem x:Key="src"/>
<local:Converter x:Key="converter"/>
</Window.Resources>
<StackPanel>
<Button Click="ChangeSource" Width="150" Margin="20">Change data source</Button>
<Polygon Fill="#CD5C5C" Points="{Binding Source={StaticResource src}, Path=Points, Converter={StaticResource converter}}"/>
</StackPanel>
When the Window is loaded, a Polygon is displayed with the points from the source collection. I also included a Button in the UI that modifies the source collection and raises a property changed notification, which causes the UI to be updated.
private void ChangeSource(object sender, RoutedEventArgs e)
{
PolygonItem polygon = this.Resources["src"] as PolygonItem;
polygon.Subdivide();
}
This is a good solution when you make big changes to the source collection of points, like the scenario I have here. If you’re simply adding or removing an item at a time from the existing collection, this solution may not scale well with very frequent changes to the source. Keep in mind that every time a property change notification is raised, we are creating a whole new PointCollection in the Converter. If you can, it’s best to batch those operations and provide only one property change notification for a group of changes.
We’ve been discussing possible solutions that would make this scenario easier and more efficient for V2. Hopefully one of those solutions will make it to our final wish list.
Here is a screenshot of this application:
Here you can find the VS project with this sample code. This works with Beta2 WPF bits.
Anonymous
Hi Beatriz. Looks like the link to your sample isn’t working.
October 6, 2006 at 3:34 am
Bea
Hi,
Thanks for letting me know. I fixed the problem, so it should be working now.
Thanks a lot!
Bea
October 6, 2006 at 9:50 am
dr5274
Hello Bea. My company is building some business objects that represent data and collections of data. Currently these objects all implement INotifyPropertyChanged, as well as a strongly typed event handler. The INotifyPropertyChanged seems to be keeping a WPF client happy, and the strongly typed event allow CLR clients to not go back to the object for data. There’s questions amongst the developers as to whether or not we should implement thest as DependencyObjects with DependencyProperties. Some feel these aren’t appropriate for business objects, and would unnecessarily tie the classes to WPF. Others think DP’s would support WPF, and not impart too much class complexity. I found a comment from Rob Relyea that seemed to question the use of DP’s for business objects, but we’d like your opinion, too. Thanks!
June 19, 2007 at 2:45 pm
Bea
Hello,
That is a great question. I share the same opinion as Rob Relyea - I don’t think you should be using DPs and DOs in your data source. If there is a possibility that you will be using those business objects with UI technologies other than WPF, you should strive for the best possible separation between UI and data, as it will save you a lot of time in the future. I have talked to several customers that ported their apps from WinForms to WPF, and the importance of keeping a clean separation of data and UI always comes up in our conversations.
If you feel that having elements specific to WPF would greatly simplify your UI layer, you may consider introducing an intermediate layer between your UI and your data. This layer collects all the information needed from the data source, and presents it to the UI layer in whatever form is most appropriate. You’ve probably heard of the Model-View-ViewModel pattern that John Gossman discusses on his blog. This is a very common technique that is used by the Blend team and other customers that are writing big WPF apps.
I hope this helps.
Bea
September 18, 2007 at 11:51 pm
Brian
I have a Collection of a Collection of points to display multiple Polygons on a Canvas with an ItemsControl.
The 2nd polygon is positioned vertically offset below the 1st polygon by the Y coordinates amount of the
lowest position in the 1st polygon. I’ve tried repositioning the Canvas.Top to 0, but it still doesn’t reset it.
I know an ItemsControl displays things in a vertical stack, but how do you override the vertical stack behavior ?
January 22, 2010 at 9:34 am
Brian
I figured it out … XAML:
<ItemsControl ItemsSource=”{Binding ElementName=mapRoot, Path=Zones}”>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Left=”0″ Top=”0″/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Polygon Canvas.ZIndex=”1″ Points=”{Binding ZonePointCollection}”
Fill=”{Binding ZoneFillColor}” Opacity=”0.02″
Stroke=”{Binding ZoneFillColor}” />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
January 22, 2010 at 10:21 am