WPF MVVM:如何将 GridViewColumn 绑定到 ViewModel-Collection?
Posted
技术标签:
【中文标题】WPF MVVM:如何将 GridViewColumn 绑定到 ViewModel-Collection?【英文标题】:WPF MVVM: how to bind GridViewColumn to ViewModel-Collection? 【发布时间】:2011-02-08 06:28:07 【问题描述】:在我的视图中,我有一个 ListView 绑定到我的 ViewModel 中的 CollectionView,例如:
<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>
现在这些 GridViewColumns 是固定的,但我希望能够从 ViewModel 更改它们。我想我必须将 GridViewColumn-collection 绑定到 ViewModel 中的某些东西,但是什么,以及如何? ViewModel 对 WPF 一无所知,所以我不知道如何在 MVVM 中实现这一点。
这里有什么帮助吗?
【问题讨论】:
【参考方案1】:Columns
属性不是依赖属性,所以不能绑定它。但是,可以创建一个附加属性,您可以将其绑定到 ViewModel 中的集合。然后,此附加属性将为您创建列。
更新
好的,这是一个基本的实现......
附加属性
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;
视图模型
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>
请注意,ColumnDescriptor
类实际上并不需要,我只是为了清楚起见添加了它,但任何类型都可以(包括匿名类型)。您只需要指定哪些属性保存标题文本并显示成员名称。
另外,请记住,它尚未经过全面测试,因此可能需要修复一些问题...
【讨论】:
呃,我不确定我是否理解如何实现您的建议。 哇,太好了,谢谢!我查了一下,你没有添加ColumnDescriptor的声明,但是正如你所说,应该很容易创建。我会在至少了解一点后立即通知;) 酷,按预期工作,太棒了!非常感谢您提供的详尽示例! 很好的例子!我将您的示例代码复制/粘贴到了一个不同的项目中,它就像一个魅力,谢谢! +1 很糟糕,它必须是这种方式,但效果很好。【参考方案2】:我采用了 Thomas Levesque 的出色解决方案并对其进行了修改,以删除 GridViews 的静态集合,并添加了设置列宽和字符串格式的功能,所以我想分享我的代码。
修改后的附加属性类:
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<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;
【讨论】:
单元格模板呢? 如果您想在创建的 GridViewColumns 上设置其他属性,然后修改附加的行为类以包含新属性并相应地修改行为类(特别是 CreateColumn)。首先,请尝试查看附加行为类(例如 WidthMember)上的属性值发生更改时会发生什么,并从那里进行跟踪。【参考方案3】:我认为这段代码会导致一些内存泄漏问题;正如您的类 GridViewColumns 所述,您已经定义了一个静态字典“_gridViewsByColumnsSource”,其中包含网格视图及其列源引用;所以这是对添加的网格视图和列源的强烈参考;因为这个字典是静态的,所以似乎一直有一个静态引用“指向”gridviews和columns源数据,如果gridview定义的屏幕关闭,则启动GC时gridview无法被GC收集;随着打开的类似屏幕越来越多,越来越多的gridview和它的columns源数据无法采集,最后会出现内存泄漏。
【讨论】:
我用内存分析器确认了这个问题。您可以通过在释放控件时将 ColumnsSource 设置为 null 来解决此问题,这会导致引用被删除(在 RemoveHandlers 方法中),或者更改 _gridViewsByColumnsSource 以保存 WeakReference 对象列表并为 ICollectionView 实现弱事件侦听器。 CollectionChanged 事件。 实际上你不需要弱事件监听器...只需要一个对 GridView 的 WeakReference。以上是关于WPF MVVM:如何将 GridViewColumn 绑定到 ViewModel-Collection?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 MVVM 中的 UserControl 之间进行通信 - WPF 应用程序