简体   繁体   English

WPF MVVM:如何将GridViewColumn绑定到ViewModel-Collection?

[英]WPF MVVM: how to bind GridViewColumn to ViewModel-Collection?

In my View I got a ListView bound to a CollectionView in my ViewModel, for example like this: 在我的View中,我在ViewModel中得到了一个绑定到CollectionView的ListView,例如:

<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. 现在这些GridViewColumns是固定的,但我希望能够从ViewModel更改它们。 I'd guess I'll have to bind the GridViewColumn-collection to something in the ViewModel, but what, and how? 我猜我必须将GridViewColumn集合绑定到ViewModel中的某些东西,但是什么,以及如何?
The ViewModel does know nothing of WPF, so I got no clue how to achieve this in MVVM. ViewModel对WPF一无所知,所以我不知道如何在MVVM中实现这一点。

any help here? 这有什么帮助吗?

The Columns property is not a dependency property, so you can't bind it. Columns属性不是依赖项属性,因此您无法绑定它。 However, it might be possible to create an attached property that you could bind to a collection in your ViewModel. 但是,可以创建一个可以绑定到ViewModel中的集合的附加属性。 This attached property would then create the columns for you. 然后,此附加属性将为您创建列。


UPDATE 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 : 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). 请注意,实际上并不需要ColumnDescriptor类,为了清楚起见我只添加了它,但任何类型都会这样做(包括匿名类型)。 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... 另外,请记住它尚未经过全面测试,因此可能存在一些问题需要解决......

I took Thomas Levesque's excellent solution and modified it to remove the static collection of GridViews and also add the ability to set a column width and string format, so thought I'd share my code. 我采用了Thomas Levesque的优秀解决方案并对其进行了修改以删除GridViews的静态集合,并添加了设置列宽和字符串格式的功能,因此我想要分享我的代码。

Modified attached property class: 修改附加属性类:

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

Behaviour (this is stored against each GridView and obviates the need to store the collection-GridView mappings centrally): 行为(这是针对每个GridView存储的,并且不需要集中存储集合-GridView映射):

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

I think this code would cause some memory leak issues; 我认为这段代码会导致一些内存泄漏问题; As your class GridViewColumns described, you've defined a static dictionary "_gridViewsByColumnsSource" in which contains the gridviews and their columns source references; 正如你所描述的GridViewColumns类,你已经定义了一个静态字典“_gridViewsByColumnsSource”,其中包含gridviews及其列的源引用; so this is a strong reference to the added gridviews and columns source; 所以这是对添加的gridviews和列源的强引用; Because this dictionary is static, it seems that there's a static reference "point" to the gridviews and columns source data all the time, if the screen in which the gridview defined closed, the gridview cannot be collected by GC if GC is started; 因为这个字典是静态的,所以似乎有一个静态引用“指向”gridviews和列源数据,如果gridview定义的屏幕关闭,如果GC启动,则无法通过GC收集gridview; As more and more similar screens opened, more and more gridviews and its columns source data cannot be collected, there'll be memory leak at the end. 随着越来越多类似的屏幕打开,越来越多的网格视图及其列源数据无法收集,最终会有内存泄漏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM