tags:

views:

1048

answers:

1

In my View I got a ListView bound to a CollectionView in my ViewModel, for example like this:

<ListView ItemsSource="{Binding MyCollection}" IsSynchronizedWithCurrentItem="true">
  <ListView.View>
    <GridView>
      <GridViewColumn Header="Title" DisplayMemberBinding="{Binding Path=Title}"/>
      <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
      <GridViewColumn Header="Phone" DisplayMemberBinding="{Binding Path=Phone}"/>
      <GridViewColumn Header="E-mail" DisplayMemberBinding="{Binding Path=EMail}"/>
    </GridView>
  </ListView.View>
</ListView>

Right now these GridViewColumns are fixed, but I'd like to be able to change them from the ViewModel. I'd guess I'll have to bind the GridViewColumn-collection to something in the ViewModel, but what, and how?
The ViewModel does know nothing of WPF, so I got no clue how to achieve this in MVVM.

any help here?

+3  A: 

The Columns property is not a dependency property, so you can't bind it. However, it might be possible to create an attached property that you could bind to a collection in your ViewModel. This attached property would then create the columns for you.


UPDATE

OK, here's a basic implementation...

Attached properties

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace TestPadWPF
{
    public static class GridViewColumns
    {
        [AttachedPropertyBrowsableForType(typeof(GridView))]
        public static object GetColumnsSource(DependencyObject obj)
        {
            return (object)obj.GetValue(ColumnsSourceProperty);
        }

        public static void SetColumnsSource(DependencyObject obj, object value)
        {
            obj.SetValue(ColumnsSourceProperty, value);
        }

        // Using a DependencyProperty as the backing store for ColumnsSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnsSourceProperty =
            DependencyProperty.RegisterAttached(
                "ColumnsSource",
                typeof(object),
                typeof(GridViewColumns),
                new UIPropertyMetadata(
                    null,
                    ColumnsSourceChanged));


        [AttachedPropertyBrowsableForType(typeof(GridView))]
        public static string GetHeaderTextMember(DependencyObject obj)
        {
            return (string)obj.GetValue(HeaderTextMemberProperty);
        }

        public static void SetHeaderTextMember(DependencyObject obj, string value)
        {
            obj.SetValue(HeaderTextMemberProperty, value);
        }

        // Using a DependencyProperty as the backing store for HeaderTextMember.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HeaderTextMemberProperty =
            DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(GridViewColumns), new UIPropertyMetadata(null));


        [AttachedPropertyBrowsableForType(typeof(GridView))]
        public static string GetDisplayMemberMember(DependencyObject obj)
        {
            return (string)obj.GetValue(DisplayMemberMemberProperty);
        }

        public static void SetDisplayMemberMember(DependencyObject obj, string value)
        {
            obj.SetValue(DisplayMemberMemberProperty, value);
        }

        // Using a DependencyProperty as the backing store for DisplayMember.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DisplayMemberMemberProperty =
            DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(GridViewColumns), new UIPropertyMetadata(null));


        private static void ColumnsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            GridView gridView = obj as GridView;
            if (gridView != null)
            {
                gridView.Columns.Clear();

                if (e.OldValue != null)
                {
                    ICollectionView view = CollectionViewSource.GetDefaultView(e.OldValue);
                    if (view != null)
                        RemoveHandlers(gridView, view);
                }

                if (e.NewValue != null)
                {
                    ICollectionView view = CollectionViewSource.GetDefaultView(e.NewValue);
                    if (view != null)
                    {
                        AddHandlers(gridView, view);
                        CreateColumns(gridView, view);
                    }
                }
            }
        }

        private static IDictionary<ICollectionView, List<GridView>> _gridViewsByColumnsSource =
            new Dictionary<ICollectionView, List<GridView>>();

        private static List<GridView> GetGridViewsForColumnSource(ICollectionView columnSource)
        {
            List<GridView> gridViews;
            if (!_gridViewsByColumnsSource.TryGetValue(columnSource, out gridViews))
            {
                gridViews = new List<GridView>();
                _gridViewsByColumnsSource.Add(columnSource, gridViews);
            }
            return gridViews;
        }

        private static void AddHandlers(GridView gridView, ICollectionView view)
        {
            GetGridViewsForColumnSource(view).Add(gridView);
            view.CollectionChanged += ColumnsSource_CollectionChanged;
        }

        private static void CreateColumns(GridView gridView, ICollectionView view)
        {
            foreach (var item in view)
            {
                GridViewColumn column = CreateColumn(gridView, item);
                gridView.Columns.Add(column);
            }
        }

        private static void RemoveHandlers(GridView gridView, ICollectionView view)
        {
            view.CollectionChanged -= ColumnsSource_CollectionChanged;
            GetGridViewsForColumnSource(view).Remove(gridView);
        }

        private static void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            ICollectionView view = sender as ICollectionView;
            var gridViews = GetGridViewsForColumnSource(view);
            if (gridViews == null || gridViews.Count == 0)
                return;

            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var gridView in gridViews)
                    {
                        for (int i = 0; i < e.NewItems.Count; i++)
                        {
                            GridViewColumn column = CreateColumn(gridView, e.NewItems[i]);
                            gridView.Columns.Insert(e.NewStartingIndex + i, column);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    foreach (var gridView in gridViews)
                    {
                        List<GridViewColumn> columns = new List<GridViewColumn>();
                        for (int i = 0; i < e.OldItems.Count; i++)
                        {
                            GridViewColumn column = gridView.Columns[e.OldStartingIndex + i];
                            columns.Add(column);
                        }
                        for (int i = 0; i < e.NewItems.Count; i++)
                        {
                            GridViewColumn column = columns[i];
                            gridView.Columns.Insert(e.NewStartingIndex + i, column);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (var gridView in gridViews)
                    {
                        for (int i = 0; i < e.OldItems.Count; i++)
                        {
                            gridView.Columns.RemoveAt(e.OldStartingIndex);
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (var gridView in gridViews)
                    {
                        for (int i = 0; i < e.NewItems.Count; i++)
                        {
                            GridViewColumn column = CreateColumn(gridView, e.NewItems[i]);
                            gridView.Columns[e.NewStartingIndex + i] = column;
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    foreach (var gridView in gridViews)
                    {
                        gridView.Columns.Clear();
                        CreateColumns(gridView, sender as ICollectionView);
                    }
                    break;
                default:
                    break;
            }
        }

        private static GridViewColumn CreateColumn(GridView gridView, object columnSource)
        {
            GridViewColumn column = new GridViewColumn();
            string headerTextMember = GetHeaderTextMember(gridView);
            string displayMemberMember = GetDisplayMemberMember(gridView);
            if (!string.IsNullOrEmpty(headerTextMember))
            {
                column.Header = GetPropertyValue(columnSource, headerTextMember);
            }
            if (!string.IsNullOrEmpty(displayMemberMember))
            {
                string propertyName = GetPropertyValue(columnSource, displayMemberMember) as string;
                column.DisplayMemberBinding = new Binding(propertyName);
            }
            return column;
        }

        private static object GetPropertyValue(object obj, string propertyName)
        {
            if (obj != null)
            {
                PropertyInfo prop = obj.GetType().GetProperty(propertyName);
                if (prop != null)
                    return prop.GetValue(obj, null);
            }
            return null;
        }
    }
}

ViewModel

class PersonsViewModel
{
    public PersonsViewModel()
    {
        this.Persons = new ObservableCollection<Person>
        {
            new Person
            {
                Name = "Doe",
                FirstName = "John",
                DateOfBirth = new DateTime(1981, 9, 12)
            },
            new Person
            {
                Name = "Black",
                FirstName = "Jack",
                DateOfBirth = new DateTime(1950, 1, 15)
            },
            new Person
            {
                Name = "Smith",
                FirstName = "Jane",
                DateOfBirth = new DateTime(1987, 7, 23)
            }
        };

        this.Columns = new ObservableCollection<ColumnDescriptor>
        {
            new ColumnDescriptor { HeaderText = "Last name", DisplayMember = "Name" },
            new ColumnDescriptor { HeaderText = "First name", DisplayMember = "FirstName" },
            new ColumnDescriptor { HeaderText = "Date of birth", DisplayMember = "DateOfBirth" }
        };
    }

    public ObservableCollection<Person> Persons { get; private set; }

    public ObservableCollection<ColumnDescriptor> Columns { get; private set; }

    private ICommand _addColumnCommand;
    public ICommand AddColumnCommand
    {
        get
        {
            if (_addColumnCommand == null)
            {
                _addColumnCommand = new DelegateCommand<string>(
                    s =>
                    {
                        this.Columns.Add(new ColumnDescriptor { HeaderText = s, DisplayMember = s });
                    });
            }
            return _addColumnCommand;
        }
    }

    private ICommand _removeColumnCommand;
    public ICommand RemoveColumnCommand
    {
        get
        {
            if (_removeColumnCommand == null)
            {
                _removeColumnCommand = new DelegateCommand<string>(
                    s =>
                    {
                        this.Columns.Remove(this.Columns.FirstOrDefault(d => d.DisplayMember == s));
                    });
            }
            return _removeColumnCommand;
        }
    }
}

XAML :

    <ListView ItemsSource="{Binding Persons}" Grid.Row="0">
        <ListView.View>
            <GridView local:GridViewColumns.HeaderTextMember="HeaderText"
                      local:GridViewColumns.DisplayMemberMember="DisplayMember"
                      local:GridViewColumns.ColumnsSource="{Binding Columns}"/>
        </ListView.View>
    </ListView>

Note that the ColumnDescriptor class is not actually needed, I only added it for clarity, but any type will do (including an anonymous type). You just need to specify which properties hold the header text and display member name.

Also, keep in mind that it's not fully tested yet, so there might be a few problems to fix...

Thomas Levesque
Uhm, I'm not really sure I understand how to achieve your suggestion.
Sam
See my updated answer for an example
Thomas Levesque
Wow, great, thanks! I'm checking it out, you did not add the declaration of ColumnDescriptor, but as you say, it should be easy to create. I'll notify as soon as I understand it at least a bit more ;)
Sam
Cool, works like expected, great! Thanks a lot for the thorough example!
Sam
Great example! I copy/pasted your sample code into a different project, and it worked like a charm, thanks!
ph0enix