2016-03-24 33 views
4

这里是我想要的:如果我将ICollectionview绑定到DataGrid,我不想在我的Viewmodel中松散SortDescription。Datagrid与ICollectionView SortDescription迷路了 - Bug?

我创建了一个小样本项目,以了解我的意思。在我的项目中,我只需使用Usercontrol在DataGrid中显示我的数据。如果我这样做,SortDescritpion不在当UserControl卸载时,因为ItemsSource设置为null。如果我使用TemplateSelector来显示我的UserControl,则SortDescription不会消失并且在卸载时ItemsSource不会设置为null。问题是,为什么这些不同的行为?是2个行为中的一个错误?

btw。我使用的.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; 
    } 


} 

回答

2

当主机内MyViewUc直接在MyGridControl(情况1) - 当切换工作区并且MyViewUC被卸载,它的datacontext被设置为null。因为MyGridControl是直接子对象 - 它的datacontext也设置为null,并依次DataGrid的DataGrid。这也将ItemsSource设置为null,因为它绑定到DataContext。您可以通过在您的行为中查看DataGrid的DataGrid来验证此情况。这种行为对我来说是完全合理的。

当您使用模板选择器:MyViewUC被卸载时,它的datacontext被设置为null。然后,ContentControl内容也设置为null。现在是这个问题:当您使用ContentTemplateSelector时,旧的(未加载的)MyGridControl的DataContext不会设置为null。你可以在你的行为中验证这一点,这就是为什么ItemsSource和排序描述符被保留下来的原因。

现在,我相信这第二个行为是不正确的,对于由ContentTemplateSelector创建的此卸载控件,datacontext应设置为null。这背后的逻辑并不是非常简单 - 您可以看看ContentPresenter.OnContentChanged方法的源代码,在内容发生更改时DataContext未更新的位置。

更新:我看到您的主要担心是丢失排序描述符,但这是丢失DataContext并将ItemsSource设置为null的直接后果。对我来说,这种行为看起来合理,但我看到确实对很多人来说是没有,所以甚至有关于这个问题的错误报告: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的保留。

+0

可以将DataContext设置为NULL,但为什么在地狱里我的SortDescritpion在我的Viewmodel中用于我的ICollectionView?这实际上是我的问题:(我不能想象多数民众党确定排序丢失,只是因为用户切换内容。 – blindmeis

+0

扩大我的答案。 – Evk

+0

thx。我在找什么,但我不喜欢我所看到的: (你认为模板选择器的解决方法有一些副作用吗? – blindmeis