Polygon Binding, Another Approach?

|

Beatriz Costa has come up with a series of articles on his blog about different ways to data bind a Polygon's Points to a data source(Part I, Part II, Part III)? in this post, I just want to show another approach to it, and my approach will get rid of all the drawbacks Beatriz Costa mentioned in his blog.

Just as Beatriz Costa mentioned in his blog, Binding to Polygon's Points property is a special case, because Points property is of PointCollection type, if we use traditional binding technique here, when the data source is changed, the change will not be reflected on to the Polygon object, and Beatriz also mentioned that we can work around this by making the collection of points as a dependency property. But this has one drawback, when you do this, you are actually make a bad assumption that your data source will be used in WPF, how about if you plan to import your application to some other presentaion technologies say Windows Forms or ASP.NET, you have to retrofit much of your code to make it proper for other presentation technologies, but if we cannot introduce any dependency in the data layer, we can actually do it in the intermediate layer, enter PointCollectionView.

My basic idea is to define a dependency property on the PointCollectionView, this DP is responsible for bridging between generic data source and UI layer, the following code illustrates:

/// <summary>
///
PointCollectionView acts like a gluing layer between
/// PolygonItem and Polygon when used in Data binding scenario.
/// </summary>
public class PointCollectionView : DependencyObject
{
    public static readonly DependencyProperty PointsProperty;
    public static readonly DependencyPropertyKey PointsPropertyKey;

    static PointCollectionView()
    {
        PointsPropertyKey = DependencyProperty.RegisterReadOnly(
            "Points",
            typeof(PointCollection),
            typeof(PointCollectionView),
            new FrameworkPropertyMetadata(null));

        PointsProperty = PointsPropertyKey.DependencyProperty;
    }

    public PointCollectionView(PolygonItem item)
    {
        base.SetValue(PointsPropertyKey, new PointCollection(item.Points));

        INotifyCollectionChanged notifyCollectionChanged = item.Points as INotifyCollectionChanged;
        if (notifyCollectionChanged != null)
        {
            notifyCollectionChanged.CollectionChanged += this.OnCollectionChanged;
        }
    }

    public PointCollection Points
    {
        get { return (PointCollection)base.GetValue(PointsProperty); }
    }

    private void OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                for (Int32 i = 0; i < e.NewItems.Count; i++)
                {
                    this.Points.Insert(e.NewStartingIndex + i, (Point)e.NewItems[i]);
                }
                break;

            case NotifyCollectionChangedAction.Move:
                for (Int32 i = 0; i < e.NewItems.Count; i++)
                {
                    this.Points.RemoveAt(e.OldStartingIndex);
                    this.Points.Insert(e.NewStartingIndex + i, (Point)e.NewItems[i]);
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (Int32 i = 0; i < e.OldItems.Count; i++)
                {
                    this.Points.RemoveAt(e.OldStartingIndex);
                }
                break;

            case NotifyCollectionChangedAction.Replace:
                for (Int32 i = 0; i < e.NewItems.Count; i++)
                {
                    this.Points[e.NewStartingIndex + i] = (Point)e.NewItems[i];
                }
                break;

            case NotifyCollectionChangedAction.Reset:
                this.Points.Clear();
                break;
        }
    }
}

You can see from the above code that the Points DP is actually a mirror to the PolygonItem.Points collection, it will listen to source collection's CollectionChanged event, and update itself accordingly, and for the usage of the PointCollectionView object, the following xaml illustrates:

<Window
  
x:Class="PolygonBinding.Window1"
   xmlns:cc="clr-namespace:PolygonBinding"
  
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title="PolygonBinding"
  
SizeToContent ="WidthAndHeight"
   
    >
  <
Window.Resources>
    <
cc:PolygonItem x:Key="polygonItem"/>
    <
ObjectDataProvider x:Key="src" ObjectType="{x:Type cc:PointCollectionView}">
      <
ObjectDataProvider.ConstructorParameters>
        <
StaticResource ResourceKey="polygonItem"/>
      </
ObjectDataProvider.ConstructorParameters>
    </
ObjectDataProvider>
    <
Style TargetType="{x:Type Polygon}">
      <
Setter Property="Points" Value="{Binding Path=Points, Source={StaticResource src}}"/>
    </
Style>
  </
Window.Resources>
    <
StackPanel>
      <
Button Click="ChangeSource" Margin="10" HorizontalAlignment="Center">Change data source</Button>
      <
Polygon Name="polygonElement" Width="500" Height="500" Margin="25" Fill="#CD5C5C"/>
    </
StackPanel>
</
Window>

The benefit of my approach here is that you can actually use the binding in a style, and in the ChangeSource event handler, you don't need to call the InvalidateMeasure() and InvalidateVisual() method to update the view of the UI,

As a side note, currently I cannot upload the whole project to my blog, because my friend have updated the communityserver.cn site without notifying me beforehand, you will find all the download links here are broken, I will fix those links when I get contact to my friend, if you want to run the source, you can first download Beatriz Costa's project, and replace those changing bits accordingly.

Edit: I just upload the vs project, you can download it from here.

0 comments: