Your Privacy Matters: We use our own and third-party cookies to improve your experience on our website. By continuing to use the website we understand that you accept their use. Cookie Policy
530
Series Binding in XAML (Custom Attached Property?)
posted

How do I bind series in XAML?  Something like an ItemsSource (SeriesSource?) property with a SeriesTemplate - to set the DataMapping and other series properties - would make the most sense to me.  If this is not possible, then does anyone have an attached property that would do the equivalent work.

Parents
  • 30692
    Verified Answer
    Offline posted

    The Series property on XamChart is read only, so although you can add items to it, you wouldn't be able to bind a collection directly to it. You are correct in your surmise, however, that there are all number of fancy things you can do with attached properties to get the effect you are after. I've had a go and maybe this will get you started:

    The Xaml:

    <Window x:Class="WpfApplication22.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="Window1" Height="300" Width="300"

            x:Name="theWindow"

        xmlns:igChart="http://infragistics.com/Chart"

            xmlns:local="clr-namespace:WpfApplication22">

        <Grid>

            <Grid.RowDefinitions>

                <RowDefinition Height="1*"/>

                <RowDefinition Height="Auto"/>

            </Grid.RowDefinitions>

           

            <igChart:XamChart x:Name="theChart">

                <local:SeriesBinder.BindingInfo>

                    <local:SeriesBindingInfo

                        ItemsSource="{Binding}">

                        <local:SeriesBindingInfo.SeriesTemplate>

                            <igChart:Series

                                ChartType="{Binding ChartType}"

                                DataMapping="{Binding DataMapping}"

                                DataSource="{Binding DataSource}">

                            </igChart:Series>

                        </local:SeriesBindingInfo.SeriesTemplate>

                    </local:SeriesBindingInfo>

                </local:SeriesBinder.BindingInfo>

            </igChart:XamChart>

           

            <Button Grid.Row="1"

                    Click="Button_Click">

                Add Series

            </Button>

        </Grid>

    </Window>

    The window's code behind:

    public partial class Window1 : Window

        {

            public Window1()

            {

                InitializeComponent();

     

                DataContext = GetSampleData();

            }

     

            private ObservableCollection<TestSeriesItem> GetSampleData()

            {

                ObservableCollection<TestSeriesItem> data = new ObservableCollection<TestSeriesItem>()

                {

                    new TestSeriesItem()

                    {

                        ChartType = ChartType.Column,

                        DataMapping = "Label = Label; Value = Value",

                        DataSource = new ObservableCollection<TestDataItem>()

                        {

                            new TestDataItem()

                            {

                                Label = "1",

                                Value = 1

                            },

                            new TestDataItem()

                            {

                                Label = "2",

                                Value = 2

                            }

                        }

                    },

                    new TestSeriesItem()

                    {

                        ChartType = ChartType.Line,

                        DataMapping = "Label = Label; Value = Value",

                        DataSource = new ObservableCollection<TestDataItem>()

                        {

                            new TestDataItem()

                            {

                                Label = "1",

                                Value = 1

                            },

                            new TestDataItem()

                            {

                                Label = "2",

                                Value = 2

                            }

                        }

                    }

                };

     

                return data;

            }

     

            private void Button_Click(object sender, RoutedEventArgs e)

            {

                ((ObservableCollection<TestSeriesItem>)DataContext).Add(

                    new TestSeriesItem()

                    {

                        ChartType = ChartType.Area,

                        DataMapping = "Label = Label; Value = Value",

                        DataSource = new ObservableCollection<TestDataItem>()

                        {

                            new TestDataItem()

                            {

                                Label = "1",

                                Value = 5

                            },

                            new TestDataItem()

                            {

                                Label = "2",

                                Value = 6

                            }

                        }

                    });

            }

        }

     

        public class TestSeriesItem

        {

            public ChartType ChartType { get; set; }

            public string DataMapping { get; set; }

            public object DataSource { get; set; }

        }

     

        public class TestDataItem

        {

            public string Label { get; set; }

            public double Value { get; set; }

        }

    And now, the attached property and helper classes:

    class DependencyPropertyEnumerator

        {

            public static IEnumerable<DependencyProperty> DependencyProperties(Type type)

            {

                var props = from prop in type.GetProperties(BindingFlags.Instance |

                               BindingFlags.Public | BindingFlags.FlattenHierarchy)

                            join field in type.GetFields(BindingFlags.Static |

                                BindingFlags.Public | BindingFlags.FlattenHierarchy)

                                on prop.Name + "Property" equals field.Name into propGroup

                            from item in propGroup.DefaultIfEmpty(null)

                            where item != null

                            select (DependencyProperty)item.GetValue(null);

     

                return props;

            }

     

            public static bool DependencyPropertyHasBinding(

                FrameworkContentElement obj,

                DependencyProperty dp,

                out BindingExpression be)

            {

                be = null;

     

                if (obj == null)

                {

                    return false;

                }

     

                be = obj.GetBindingExpression

    (dp);

                if (be != null)

                {

                    return true;

                }

     

                return false;

            }

     

            public static bool DependencyPropertyHasValue(

                FrameworkContentElement obj,

                DependencyProperty dp, out object value)

            {

                value = null;

     

                if (obj == null)

                {

                    return false;

                }

     

                object localValue = obj.ReadLocalValue(dp);

                object defaultValue =

                    dp.GetMetadata(typeof(Series)).DefaultValue;

     

                if (localValue != DependencyProperty.UnsetValue)

                {

                    if ((localValue == null && defaultValue != null)

                        || !localValue.Equals(defaultValue))

                    {

                        value = localValue;

                        return true;

                    }

                }

                return false;

            }

        }

     

        class SeriesBindingInfo

            : FrameworkElement

        {

            private XamChart targetChart;

            public XamChart TargetChart

            {

                get

                {

                    return targetChart;

                }

                set

                {

                    targetChart = value;

                    //bridge the data context from the parent hierarchy.

                    Binding binding = new Binding("DataContext")

                    {

                        Source = targetChart

                    };

                    SetBinding(DataContextProperty, binding);

                    Clear();

                    UpdateFromCollection(ItemsSource);

                }

            }

     

            public static DependencyProperty ItemsSourceProperty =

                DependencyProperty.Register("ItemsSource",

                typeof(IEnumerable), typeof(SeriesBindingInfo),

                new FrameworkPropertyMetadata(null,

                    (o, e) =>

                    {

                        (o as SeriesBindingInfo).OnItemsSourceChanged(e);

                    }));

     

            public IEnumerable ItemsSource

            {

                get

                {

                    return (IEnumerable)GetValue(ItemsSourceProperty);

                }

                set

                {

                    SetValue(ItemsSourceProperty, value);

                }

            }

     

            public static DependencyProperty SeriesTemplateProperty =

                DependencyProperty.Register("SeriesTemplate",

                typeof(Series), typeof(SeriesBindingInfo),

                new FrameworkPropertyMetadata(null,

                    (o, e) =>

                    {

                        (o as SeriesBindingInfo).OnSeriesTemplateChanged(e);

                    }

                        ));

     

            public Series SeriesTemplate

            {

                get

                {

                    return (Series)GetValue(SeriesTemplateProperty);

                }

                set

                {

                    SetValue(SeriesTemplateProperty, value);

                }

            }

     

            protected void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)

            {

                Clear();

     

                if (e.OldValue != null)

                {

                    UnRegCollection(e.OldValue as IEnumerable);

                }

                if (e.NewValue != null)

                {

                    RegCollection(e.NewValue as IEnumerable);

                    UpdateFromCollection(e.NewValue as IEnumerable);

                }

            }

     

            protected void OnSeriesTemplateChanged(DependencyPropertyChangedEventArgs e)

            {

                Clear();

                UpdateFromCollection(ItemsSource);

            }

     

            private void Clear()

            {

                if (TargetChart == null)

                {

                    return;

                }

     

                TargetChart.Series.Clear();

            }

     

            private void UpdateFromCollection(IEnumerable collection)

            {

                if (collection == null)

                {

                    return;

                }

                foreach (object item in collection)

                {

                    AddItem(item);

                }

            }

     

            private void AddItem(object item)

            {

                if (SeriesTemplate == null)

                {

                    return;

                }

     

                Series series = new Series();

                series.DataContext = item;

     

                SetSeriesBindings(series);

                TargetChart.Series.Add(series);

            }       

     

            private void SetSeriesBindings(Series series)

            {

                if (SeriesTemplate == null)

                {

                    return;

                }

                foreach (DependencyProperty dp in

                    DependencyPropertyEnumerator.DependencyProperties(typeof(Series)))

                {

                    object value;

                    BindingExpression be;

     

                    if (DependencyPropertyEnumerator.

                        DependencyPropertyHasBinding(

                        SeriesTemplate, dp, out be))

                    {

                        series.SetBinding(dp, be.ParentBinding);

                    }

                    else if (DependencyPropertyEnumerator.

                        DependencyPropertyHasValue(

                        SeriesTemplate, dp, out value))

                    {

                        series.SetValue(dp, value);

                    }

                }

            }

     

            private void RegCollection(IEnumerable collection)

            {

                if (collection is INotifyCollectionChanged)

                {

                    (collection as INotifyCollectionChanged).CollectionChanged

                        += new NotifyCollectionChangedEventHandler(

                            SeriesBindingInfo_CollectionChanged);

                }

            }

     

            private void UnRegCollection(IEnumerable collection)

            {

                if (collection is INotifyCollectionChanged)

                {

                    (collection as INotifyCollectionChanged).CollectionChanged

                        -= new NotifyCollectionChangedEventHandler(

                            SeriesBindingInfo_CollectionChanged);

                }

            }

     

            void SeriesBindingInfo_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

            {

                if (TargetChart == null)

                {

                    return;

                }

                //this could be made more efficient of course.

                Clear();

                UpdateFromCollection(ItemsSource);

            }

        }

     

        class SeriesBinder

            : DependencyObject

        {

            public static DependencyProperty BindingInfoProperty =

                DependencyProperty.RegisterAttached("BindingInfo",

                typeof(SeriesBindingInfo), typeof(SeriesBinder),

                new PropertyMetadata(null,

                    (o, e) =>

                    {

                        if (o is XamChart)

                        {

                            SeriesBindingInfo prev = GetBindingInfo(o);

                            if (prev != null && prev.TargetChart != null)

                            {

                                prev.TargetChart = null;

                            }

                            if (e.NewValue != null)

                            {

                                (e.NewValue as SeriesBindingInfo).TargetChart =

                                    (XamChart)o;

                            }

                        }

                    }));

     

            public static void SetBindingInfo(DependencyObject target, SeriesBindingInfo info)

            {

                target.SetValue(BindingInfoProperty, info);

            }

     

            public static SeriesBindingInfo GetBindingInfo(DependencyObject target)

            {

                return (SeriesBindingInfo)target.GetValue(BindingInfoProperty);

            }

        }

    Basically what's going on here is that the SeriesBindingInfo that you attach to the chart will listen to changes in its ItemsSource and create series objects based on a template series you provide, making sure that the data context is set to the respective item in the ItemsSource. It creates a series based on the template by basically copying any dependency property values and bindings into a new Series object. It doesn't do anything for CLR properties currently.

    Is this the sort of thing you were thinking of?

    -Graham

  • 530
    posted in reply to Graham Murray

    This is phenomenal!!!  Seriously.  Great work, Graham.

    I wasn't sure I'd get a response, so I began working on my own implementation.  Interestingly, mine is called "ChartBinder" with a Dependency property (Attached) named "ChartSeriesSourceProperty" of type "ChartSeriesCollection."  The ChartSeriesCollection is an ObservableCollection and has a Chart:XamChart property.  I watch the collection and add/remove chart series as necessary.

    Your method is much more generic and, well, elegant.  I particularly like the templating part.  I'm going to give it a try.

    I was looking at other 3rd party controls for this same functionality.  Only one other major vendor seems to do it, but it is a very awkward implementation.

    Thanks!

Reply Children