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

在我的View中,我在ViewModel中有一个绑定到CollectionView的ListView,例如:

          

现在这些GridViewColumns是固定的,但我希望能够从ViewModel更改它们。 我想我必须将GridViewColumn集合绑定到ViewModel中的某些东西,但是什么,以及如何?
ViewModel对WPF一无所知,所以我不知道如何在MVVM中实现这一点。

这有什么帮助吗?

Columns属性不是依赖项属性,因此您无法绑定它。 但是,可以创建一个可以绑定到ViewModel中的集合的附加属性。 然后,此附加属性将为您创建列。


UPDATE

好的,这是一个基本的实现……

附属物

 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> _gridViewsByColumnsSource = new Dictionary>(); private static List GetGridViewsForColumnSource(ICollectionView columnSource) { List gridViews; if (!_gridViewsByColumnsSource.TryGetValue(columnSource, out gridViews)) { gridViews = new List(); _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 columns = new List(); 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; } } } 

视图模型

 class PersonsViewModel { public PersonsViewModel() { this.Persons = new ObservableCollection { 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 { new ColumnDescriptor { HeaderText = "Last name", DisplayMember = "Name" }, new ColumnDescriptor { HeaderText = "First name", DisplayMember = "FirstName" }, new ColumnDescriptor { HeaderText = "Date of birth", DisplayMember = "DateOfBirth" } }; } public ObservableCollection Persons { get; private set; } public ObservableCollection Columns { get; private set; } private ICommand _addColumnCommand; public ICommand AddColumnCommand { get { if (_addColumnCommand == null) { _addColumnCommand = new DelegateCommand( 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( s => { this.Columns.Remove(this.Columns.FirstOrDefault(d => d.DisplayMember == s)); }); } return _removeColumnCommand; } } } 

XAML:

       

请注意,实际上并不需要ColumnDescriptor类,为了清楚起见我只添加了它,但任何类型都会这样做(包括匿名类型)。 您只需指定哪些属性包含标题文本和显示成员名称。

另外,请记住它尚未经过全面测试,因此可能存在一些问题需要解决......

我采用了Thomas Levesque的优秀解决方案并对其进行了修改以删除GridViews的静态集合,并添加了设置列宽和字符串格式的function,所以我想我会分享我的代码。

修改附加属性类:

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

行为(这是针对每个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 columns = new List(); 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; } } 

我认为这段代码会导致一些内存泄漏问题; 正如你所描述的GridViewColumns类,你已经定义了一个静态字典“_gridViewsByColumnsSource”,其中包含gridviews及其列的源引用; 所以这是对添加的gridviews和列源的强引用; 因为这个字典是静态的,所以似乎有一个静态引用“指向”gridviews和列源数据,如果gridview定义的屏幕关闭,如果GC启动,则无法通过GC收集gridview; 随着越来越多类似的屏幕打开,越来越多的网格视图及其列源数据无法收集,最终会有内存泄漏。