这就是我想要的:如果我将ICollectionview绑定到DataGrid,我不想在我的Viewmodel中松开SortDescription。
我创建了一个小样本项目,看看我的意思。在我的项目中,我只需使用Usercontrol在DataGrid中显示我的数据。如果我执行此操作,则在UserControl卸载时 SortDescritpion消失,因为ItemsSource设置为null。如果我使用TemplateSelector来显示我的UserControl, SortDescription不会消失,并且在Unload上ItemsSource不会设置为null。问题是,为什么这些不同的行为?这两个行为中的一个是一个错误吗?
顺便说一句。我使用.Net 4.5.1但安装了4.6.1并且system.Windows.Interactivity 4.0.0.0
MainWindow.xaml
<Window x:Class="DataGridICollectionView.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataGridICollectionView"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewmodelListe}">
<local:MyViewUc/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ToolBar Grid.Row="0">
<Button Content="SetWorkspace MyView" Click="Button_Click"/>
<Button Content="SetWorkspace Other" Click="Button_Click_1"/>
</ToolBar>
<ContentPresenter Grid.Row="1" Content="{Binding Workspace}"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace DataGridICollectionView
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private object _workspace;
public MainWindow()
{
InitializeComponent();
MyViewVm = new ViewmodelListe();
DataContext = this;
}
public ViewmodelListe MyViewVm { get; set; }
public object Workspace
{
get { return _workspace; }
set
{
_workspace = value;
OnPropertyChanged();
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Workspace = MyViewVm;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Workspace = "Other";
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ViewmodelListe : INotifyPropertyChanged
{
public ViewmodelListe()
{
Persons = new ObservableCollection<Person>();
MyView = CollectionViewSource.GetDefaultView(Persons);
Persons.Add(new Person() {FirstName = "P1", LastName = "L1"});
Persons.Add(new Person() {FirstName = "P2", LastName = "L2"});
Persons.Add(new Person() {FirstName = "P3", LastName = "L3"});
}
public ObservableCollection<Person> Persons { get; private set; }
public ICollectionView MyView { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Person : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
OnPropertyChanged();
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class TestBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Unloaded += AssociatedObjectUnloaded;
}
private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)
{
//look at this in Debug Mode, its NULL if you dont use the TemplateSelector
var itemssource = AssociatedObject.ItemsSource;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
}
}
}
MyGridControl.xaml
<UserControl x:Class="DataGridICollectionView.MyGridControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DataGridICollectionView"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<DataGrid ItemsSource="{Binding MyView}" AutoGenerateColumns="True">
<i:Interaction.Behaviors>
<local:TestBehavior/>
</i:Interaction.Behaviors>
</DataGrid>
</Grid>
</UserControl>
MyViewUc.xaml
<UserControl x:Class="DataGridICollectionView.MyViewUc"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:DataGridICollectionView"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate x:Key="MyViewCrap">
<local:MyGridControl/>
</DataTemplate>
<local:MyTemplateSelector x:Key="Selector" GridView="{StaticResource MyViewCrap}" />
</UserControl.Resources>
<Grid>
<!--When using Contentcontrol with TemplateSelector- ItemsSource is NOT set to null -->
<ContentControl Content="{Binding .}" ContentTemplateSelector="{StaticResource Selector}"/>
<!--When using MyGridControl withOUT TemplateSelector- ItemsSource is set to NULL -->
<!--<local:MyGridControl/>-->
</Grid>
</UserControl>
MyViewUc.xaml.cs
namespace DataGridICollectionView
{
/// <summary>
/// Interaktionslogik für MyViewUc.xaml
/// </summary>
public partial class MyViewUc : UserControl
{
public MyViewUc()
{
InitializeComponent();
}
}
public class MyTemplateSelector : DataTemplateSelector
{
public DataTemplate GridView { get; set; }
public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
var chooser = item as ViewmodelListe;
if (chooser == null)
{
return base.SelectTemplate(item, container);
}
return GridView;
}
}
}
编辑:我最终使用此
public class MyDataGrid : DataGrid
{
static MyDataGrid ()
{
ItemsSourceProperty.OverrideMetadata(typeof(MyDataGrid ),new FrameworkPropertyMetadata(null, OnPropertyChangedCallBack, OnCoerceItemsSourceProperty));
}
private ICollectionView _defaultView;
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if(_defaultView != null)
_defaultView.CollectionChanged -= LiveSortingPropertiesOnCollectionChanged;
base.OnItemsSourceChanged(oldValue, newValue);
_defaultView = newValue as ICollectionView;
if(_defaultView != null)
_defaultView.CollectionChanged += LiveSortingPropertiesOnCollectionChanged;
}
private void LiveSortingPropertiesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
foreach (var dataGridColumn in this.Columns)
{
var isSortDirectionSetFromCollectionView = false;
foreach (var sortDescription in _defaultView.SortDescriptions)
{
if (dataGridColumn.SortMemberPath == sortDescription.PropertyName)
{
dataGridColumn.SortDirection = sortDescription.Direction;
isSortDirectionSetFromCollectionView = true;
break;
}
}
if (!isSortDirectionSetFromCollectionView)
{
dataGridColumn.SortDirection = null;
}
}
}
}
private static void OnPropertyChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grd = d as MyDataGrid ;
var view = e.NewValue as ICollectionView;
if (grd == null || view == null)
return;
foreach (var dataGridColumn in grd.Columns)
{
var isSortDirectionSetFromCollectionView = false;
foreach (var sortDescription in view.SortDescriptions)
{
if (dataGridColumn.SortMemberPath == sortDescription.PropertyName)
{
dataGridColumn.SortDirection = sortDescription.Direction;
isSortDirectionSetFromCollectionView = true;
break;
}
}
//wenn die View nicht sortiert war, auch die column nicht Sortieren
if (!isSortDirectionSetFromCollectionView)
{
dataGridColumn.SortDirection = null;
}
}
}
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
// do nothing here - we just want to override parent behaviour.
// The _only_ thing DataGrid does here is clearing sort descriptors
return baseValue;
}
}
答案 0 :(得分:3)
直接在MyViewUc中托管MyGridControl(案例1) - 当您切换工作区并卸载MyViewUC时,它的datacontext设置为null。因为MyGridControl是直接子节点 - 它的datacontext也设置为null,而DataGrid则设置为DataContext。这也将ItemsSource设置为null,因为它绑定到DataContext。您可以通过在行为中查看DataGrid的DataContext来验证这一点。这种行为在我看来是完全合理的。
使用模板选择器时:MyViewUC已卸载,它的datacontext设置为null。然后ContentControl Content也设置为null。现在问题是:当您使用ContentTemplateSelector时,旧的(卸载的)MyGridControl的DataContext不会设置为null。您可以在行为中验证这一点,这就是保留ItemsSource和排序描述符的原因。
现在,我认为第二种行为是不正确的,并且对于ContentTemplateSelector创建的这个卸载控件,应该将datacontext设置为null。这背后的逻辑并不是很简单 - 您可以查看ContentPresenter.OnContentChanged方法的源代码,在这里您将看到内容更改时DataContext未更新的时间。
更新:我发现你主要担心的是丢失排序描述符,但这是丢失DataContext并将ItemsSource设置为null的直接后果。对我来说,这种行为看起来很合理,但我确实看到很多人确实没有,所以甚至有关于这个问题的bug报告:https://connect.microsoft.com/VisualStudio/feedback/details/592897/collectionviewsource-sorting-only-the-first-time-it-is-bound-to-a-source
您可以在DataGrid源代码中看到自己:
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (newValue == null)
this.ClearSortDescriptionsOnItemsSourceChange();
// more code here....
}
因此,当您将ItemsSource设置为null时,将显式清除所有排序描述符。在上面的链接中,您可以找到一些有用的解决方法。
UPDATE2:您可以考虑通过继承DataGrid来尝试修复该行为。我不是说这是完美的解决方案,但两者都没有使用ContentTemplateSelector。当ItemsSource设置为null时,有两个地方可以清除排序描述符 - 在OnItemsSourceChanged和OnCoerceItemsSourceProperty中。所以你可以做到以下几点:
public class MyDataGrid : DataGrid {
static MyDataGrid() {
ItemsSourceProperty.OverrideMetadata(typeof(MyDataGrid), new FrameworkPropertyMetadata(null, OnCoerceItemsSourceProperty));
}
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue) {
// do nothing here - we just want to override parent behaviour.
// The _only_ thing DataGrid does here is clearing sort descriptors
return baseValue;
}
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) {
SortDescription[] sorts = null;
if (newValue == null) {
// preserve sort descriptors when setting ItemsSource to null
sorts = Items.SortDescriptions.ToArray();
}
// they will now be cleared here
base.OnItemsSourceChanged(oldValue, newValue);
if (sorts != null) {
// restore them back
foreach (var sort in sorts) {
Items.SortDescriptions.Add(sort);
}
}
}
}
使用上面的代码,您将看到在切换datacontext之间,MyView中保留了排序描述符。