DataGrid
。(这个设置是相当标准的:DataGrid 的
ItemSource
属性绑定到一个 ObservableCollection
上;列使用 DataGridTextColumns
;DataGrid 内部的数据对 ObservableCollection 中的更改做出了正确反应;通过鼠标点击可以正常进行排序)有什么想法吗?
DataGrid
。ItemSource
属性绑定到一个 ObservableCollection
上;列使用 DataGridTextColumns
;DataGrid 内部的数据对 ObservableCollection 中的更改做出了正确反应;通过鼠标点击可以正常进行排序)我花了整个下午,但最终我找到了一个出人意料的简单,短小精悍且高效的解决方案:
要控制所涉及的UI控件(在这里是DataGrid
)的行为,可以简单地使用CollectionViewSource
。它作为ViewModel中UI控件的代表,而不完全打破MVVM模式。
在ViewModel中声明一个CollectionViewSource
和一个普通的ObservableCollection<T>
,并将CollectionViewSource
包装在ObservableCollection
周围:
// Gets or sets the CollectionViewSource
public CollectionViewSource ViewSource { get; set; }
// Gets or sets the ObservableCollection
public ObservableCollection<T> Collection { get; set; }
// Instantiates the objets.
public ViewModel () {
this.Collection = new ObservableCollection<T>();
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.Collection;
}
然后在应用程序的视图部分,你只需要将CollectionControl
的ItemsSource
绑定到CollectionViewSource
的视图属性,而不是直接绑定到ObservableCollection
,其他什么都不用做:
<DataGrid ItemsSource="{Binding ViewSource.View}" />
从现在开始,您可以在ViewModel中使用CollectionViewSource
对象来直接操作View中的UI控件。
例如,排序-就像我的主要问题一样-将如下所示:
// Specify a sorting criteria for a particular column
ViewSource.SortDescriptions.Add(new SortDescription ("columnName", ListSortDirection.Ascending));
// Let the UI control refresh in order for changes to take place.
ViewSource.View.Refresh();
你看,非常简单和直观。希望这可以像帮助我一样帮助其他人。
这更多是为了澄清而不是回答,但 WPF 始终 绑定到 ICollectionView
而不是源集合。 CollectionViewSource
是用于创建/检索集合视图的机制。
这是一个关于该主题的好资源,它应该帮助你更好地使用 WPF 中的集合视图:http://bea.stollnitz.com/blog/?p=387
在 XAML 中使用 CollectionViewSource
实际上可以简化你的代码:
<Window.Resources>
<CollectionViewSource Source="{Binding MySourceCollection}" x:Key="cvs">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="ColumnName" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<DataGrid ItemsSource="{Binding Source={StaticResource cvs}}">
</DataGrid>
有人认为,遵循MVVM模式时,视图模型应该始终公开集合视图,但我认为这取决于使用情况。如果视图模型永远不会直接与集合视图交互,那么在XAML中配置它会更容易。sellmeadog的回答要么过于复杂,要么已经过时了。其实很简单,你只需要:
<UserControl.Resources>
<CollectionViewSource
Source="{Binding MyCollection}"
IsLiveSortingRequested="True"
x:Key="MyKey" />
</UserControl.Resources>
<DataGrid ItemsSource="{Binding Source={StaticResource MyKey} }" >...
我看不到任何明显简单的方法,所以我会尝试使用附加行为。这有点不太正常,但可以给你想要的结果:
public static class DataGridAttachedProperty
{
public static DataGrid _storedDataGrid;
public static Boolean GetResortOnCollectionChanged(DataGrid dataGrid)
{
return (Boolean)dataGrid.GetValue(ResortOnCollectionChangedProperty);
}
public static void SetResortOnCollectionChanged(DataGrid dataGrid, Boolean value)
{
dataGrid.SetValue(ResortOnCollectionChangedProperty, value);
}
/// <summary>
/// Exposes attached behavior that will trigger resort
/// </summary>
public static readonly DependencyProperty ResortOnCollectionChangedProperty =
DependencyProperty.RegisterAttached(
"ResortOnCollectionChangedProperty", typeof (Boolean),
typeof(DataGridAttachedProperty),
new UIPropertyMetadata(false, OnResortOnCollectionChangedChange));
private static void OnResortOnCollectionChangedChange
(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
_storedDataGrid = dependencyObject as DataGrid;
if (_storedDataGrid == null)
return;
if (e.NewValue is Boolean == false)
return;
var observableCollection = _storedDataGrid.ItemsSource as ObservableCollection;
if(observableCollection == null)
return;
if ((Boolean)e.NewValue)
observableCollection.CollectionChanged += OnCollectionChanged;
else
observableCollection.CollectionChanged -= OnCollectionChanged;
}
private static void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.OldItems == e.NewItems)
return;
_storedDataGrid.Items.Refresh()
}
}
然后,您可以通过以下方式附加它:
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter
Property="AttachedProperties:DataGridAttachedProperty.ResortOnCollectionChangedProperty"
Value="true"
/>
</Style>
</DataGrid.Style>
_storedDataGrid.ItemsCollection.Refresh()
。但由于某种原因,DataGrid无法排序。我不确定,但可能是因为我正在使用多个线程,并且在将Refresh调用传播到UIThread时遇到了一些问题。无论如何,我找到了另一个很好的解决方案-请参阅我的答案。再次感谢您! - marc wellman对于其他遇到这个问题的人,以下内容可能会帮助你开始解决... 如果你有一个 INotifyPropertyChanged 集合,你可以使用它来代替 ObservableCollection - 当集合中的单个项发生更改时,它将自动刷新: 注意:由于这会将项标记为已删除然后重新添加(即使它们实际上并没有被删除和添加),所以选择可能会失去同步。对于我的小型个人项目来说已经足够好了,但还不准备发布给客户...
public class ObservableCollection2<T> : ObservableCollection<T>
{
public ObservableCollection2()
{
this.CollectionChanged += ObservableCollection2_CollectionChanged;
}
void ObservableCollection2_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (object o in e.OldItems)
remove(o);
if (e.NewItems != null)
foreach (object o in e.NewItems)
add(o);
}
void add(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if(ipc!=null)
ipc.PropertyChanged += Ipc_PropertyChanged;
}
void remove(object o)
{
INotifyPropertyChanged ipc = o as INotifyPropertyChanged;
if (ipc != null)
ipc.PropertyChanged -= Ipc_PropertyChanged;
}
void Ipc_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyCollectionChangedEventArgs f;
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, sender);
base.OnCollectionChanged(f);
f = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, sender);
base.OnCollectionChanged(f);
}
}
CollectionViewSource
? - WPF-itCollectionViewSource
解决我的问题。请查看我的答案以了解我所做的。谢谢! - marc wellman