WPF MVVM: comment lier GridViewColumn à ViewModel-Collection?

dans mon View j'ai une ListView liée à une CollectionView dans mon ViewModel, par exemple comme ceci:

<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>

en ce moment ces GridViewColumns sont fixes, mais j'aimerais pouvoir les changer à partir du ViewModel. Je suppose que je vais devoir lier la collection GridViewColumn à quelque chose dans le ViewModel, mais quoi, et comment?

Le ViewModel ne connaît rien de la WPF, donc je n'ai aucune idée de comment réaliser ceci dans MVVM.

toute l'aide ici?

20
demandé sur Reza M. 2010-04-15 11:52:54

3 réponses

Columns la propriété n'est pas une propriété de dépendance, donc vous ne pouvez pas la lier. Cependant, il pourrait être possible de créer une propriété jointe que vous pourriez lier à une collection dans votre ViewModel. Cette propriété jointe créerait alors les colonnes pour vous.


UPDATE

OK, voici une implémentation de base...

propriétés Attachées

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>

notez que le ColumnDescriptor class n'est pas réellement nécessaire, je l'ai seulement ajouté pour plus de clarté, mais n'importe quel type fera l'affaire (y compris un type anonyme). Vous avez juste besoin de spécifier quelles propriétés contiennent le texte de l'en-tête et le nom du membre d'affichage.

Aussi, gardez à l'esprit qu'il n'est pas totalement encore testé, alors il pourrait y avoir quelques problèmes à résoudre...

36
répondu Thomas Levesque 2010-04-15 13:11:42

J'ai pris L'excellente solution de Thomas Levesque et l'ai modifié pour supprimer la collection statique de GridViews et aussi ajouter la capacité de définir une largeur de colonne et le format de chaîne, donc pensé que je partagerais mon code.

Modifié attachés à la propriété de la classe:

public static class GridViewColumnCollection
{
    public static readonly DependencyProperty ColumnCollectionBehaviourProperty =
        DependencyProperty.RegisterAttached("ColumnCollectionBehaviour", typeof(GridViewColumnCollectionBehaviour), typeof(GridViewColumnCollection), new UIPropertyMetadata(null));

    public static readonly DependencyProperty ColumnsSourceProperty =
        DependencyProperty.RegisterAttached("ColumnsSource", typeof(object), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.ColumnsSourceChanged));

    public static readonly DependencyProperty DisplayMemberFormatMemberProperty =
        DependencyProperty.RegisterAttached("DisplayMemberFormatMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.DisplayMemberFormatMemberChanged));

    public static readonly DependencyProperty DisplayMemberMemberProperty =
        DependencyProperty.RegisterAttached("DisplayMemberMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.DisplayMemberMemberChanged));

    public static readonly DependencyProperty HeaderTextMemberProperty =
        DependencyProperty.RegisterAttached("HeaderTextMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.HeaderTextMemberChanged));

    public static readonly DependencyProperty WidthMemberProperty =
        DependencyProperty.RegisterAttached("WidthMember", typeof(string), typeof(GridViewColumnCollection), new UIPropertyMetadata(null, GridViewColumnCollection.WidthMemberChanged));

    [AttachedPropertyBrowsableForType(typeof(GridView))]
    public static GridViewColumnCollectionBehaviour GetColumnCollectionBehaviour(DependencyObject obj)
    {
        return (GridViewColumnCollectionBehaviour)obj.GetValue(ColumnCollectionBehaviourProperty);
    }

    public static void SetColumnCollectionBehaviour(DependencyObject obj, GridViewColumnCollectionBehaviour value)
    {
        obj.SetValue(ColumnCollectionBehaviourProperty, value);
    }

    [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);
    }

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

    public static void SetDisplayMemberFormatMember(DependencyObject obj, string value)
    {
        obj.SetValue(DisplayMemberFormatMemberProperty, value);
    }

    [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);
    }

    [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);
    }

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

    public static void SetWidthMember(DependencyObject obj, string value)
    {
        obj.SetValue(WidthMemberProperty, value);
    }

    private static void ColumnsSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).ColumnsSource = e.NewValue;
    }

    private static void DisplayMemberFormatMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).DisplayMemberFormatMember = e.NewValue as string;
    }

    private static void DisplayMemberMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).DisplayMemberMember = e.NewValue as string;
    }

    private static void HeaderTextMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).HeaderTextMember = e.NewValue as string;
    }

    private static void WidthMemberChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        GridViewColumnCollection.GetOrCreateColumnCollectionBehaviour(sender).WidthMember = e.NewValue as string;
    }

    private static GridViewColumnCollectionBehaviour GetOrCreateColumnCollectionBehaviour(DependencyObject source)
    {
        GridViewColumnCollectionBehaviour behaviour = GetColumnCollectionBehaviour(source);

        if (behaviour == null)
        {
            GridView typedSource = source as GridView;

            if (typedSource == null)
            {
                throw new Exception("This property can only be set on controls deriving GridView");
            }

            behaviour = new GridViewColumnCollectionBehaviour(typedSource);

            SetColumnCollectionBehaviour(typedSource, behaviour);
        }

        return behaviour;
    }
}

comportement (ce qui est stocké contre chaque GridView et évite la nécessité de stocker les mappages collection-GridView au niveau central):

public class GridViewColumnCollectionBehaviour
{
    private object columnsSource;
    private GridView gridView;

    public GridViewColumnCollectionBehaviour(GridView gridView)
    {
        this.gridView = gridView;
    }

    public object ColumnsSource
    {
        get { return this.columnsSource; }
        set
        {
            object oldValue = this.columnsSource;
            this.columnsSource = value;
            this.ColumnsSourceChanged(oldValue, this.columnsSource);
        }
    }

    public string DisplayMemberFormatMember { get; set; }

    public string DisplayMemberMember { get; set; }

    public string HeaderTextMember { get; set; }

    public string WidthMember { get; set; }

    private void AddHandlers(ICollectionView collectionView)
    {
        collectionView.CollectionChanged += this.ColumnsSource_CollectionChanged;
    }

    private void ColumnsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        ICollectionView view = sender as ICollectionView;

        if (this.gridView == null)
        {
            return;
        }

        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    GridViewColumn column = CreateColumn(e.NewItems[i]);
                    gridView.Columns.Insert(e.NewStartingIndex + i, column);
                }
                break;
            case NotifyCollectionChangedAction.Move:
                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:
                for (int i = 0; i < e.OldItems.Count; i++)
                {
                    gridView.Columns.RemoveAt(e.OldStartingIndex);
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    GridViewColumn column = CreateColumn(e.NewItems[i]);

                    gridView.Columns[e.NewStartingIndex + i] = column;
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                gridView.Columns.Clear();
                CreateColumns(sender as ICollectionView);
                break;
            default:
                break;
        }
    }

    private void ColumnsSourceChanged(object oldValue, object newValue)
    {
        if (this.gridView != null)
        {
            gridView.Columns.Clear();

            if (oldValue != null)
            {
                ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);

                if (view != null)
                {
                    this.RemoveHandlers(view);
                }
            }

            if (newValue != null)
            {
                ICollectionView view = CollectionViewSource.GetDefaultView(newValue);

                if (view != null)
                {
                    this.AddHandlers(view);

                    this.CreateColumns(view);
                }
            }
        }
    }

    private GridViewColumn CreateColumn(object columnSource)
    {
        GridViewColumn column = new GridViewColumn();

        if (!string.IsNullOrEmpty(this.HeaderTextMember))
        {
            column.Header = GetPropertyValue(columnSource, this.HeaderTextMember);
        }

        if (!string.IsNullOrEmpty(this.DisplayMemberMember))
        {
            string propertyName = GetPropertyValue(columnSource, this.DisplayMemberMember) as string;

            string format = null;

            if (!string.IsNullOrEmpty(this.DisplayMemberFormatMember))
            {
                format = GetPropertyValue(columnSource, this.DisplayMemberFormatMember) as string;
            }

            if (string.IsNullOrEmpty(format))
            {
                format = "{0}";
            }

            column.DisplayMemberBinding = new Binding(propertyName) { StringFormat = format };
        }

        if (!string.IsNullOrEmpty(this.WidthMember))
        {
            double width = (double)GetPropertyValue(columnSource, this.WidthMember);
            column.Width = width;
        }

        return column;
    }

    private void CreateColumns(ICollectionView collectionView)
    {
        foreach (object item in collectionView)
        {
            GridViewColumn column = this.CreateColumn(item);

            this.gridView.Columns.Add(column);
        }
    }

    private object GetPropertyValue(object obj, string propertyName)
    {
        object returnVal = null;

        if (obj != null)
        {
            PropertyInfo prop = obj.GetType().GetProperty(propertyName);

            if (prop != null)
            {
                returnVal = prop.GetValue(obj, null);
            }
        }

        return returnVal;
    }

    private void RemoveHandlers(ICollectionView collectionView)
    {
        collectionView.CollectionChanged -= this.ColumnsSource_CollectionChanged;
    }
}
6
répondu Dan Parsonson 2012-07-27 11:45:36

je pense que ce code serait la cause de certains problèmes de fuite de mémoire; Que votre classe GridViewColumns décrit, vous avez défini un dictionnaire statique "_gridViewsByColumnsSource" dans lequel contient le gridviews et leurs colonnes de la source de références; c'est donc une référence forte à l'ajout gridviews et les colonnes de la source; Parce que ce dictionnaire est statique, il semble qu'il y a un point de référence fixe "point" à la gridviews et les colonnes de la source de données tout le temps, si l'écran dans lequel le gridview défini fermé, le gridview ne peut pas être collecté par GC si GC est lancé; comme de plus en plus d'écrans similaires ouverts, gridviews de plus en plus et ses colonnes de données source ne peut pas être collecté, il y aura une fuite de mémoire à la fin.

2
répondu cswang 2011-12-07 16:22:26