简体   繁体   English

带有ICollectionView SortDescription的Datagrid丢失了 - Bug?

[英]Datagrid with ICollectionView SortDescription got lost - Bug?

here is what i want: if i bind a ICollectionview to a DataGrid, i dont wanna loose the SortDescription in my Viewmodel. 这就是我想要的:如果我将ICollectionview绑定到DataGrid,我不想在我的Viewmodel中松开SortDescription。

i create a small sample project to see what i mean. 我创建了一个小样本项目,看看我的意思。 In my projects i simply use a Usercontrol to show my data in a DataGrid. 在我的项目中,我只需使用Usercontrol在DataGrid中显示我的数据。 If i do this the SortDescritpion is gone when the UserControl Unload, because the ItemsSource is set to null. 如果我这样做,当UserControl卸载时, SortDescritpion就消失了 ,因为ItemsSource设置为null。 If i use a TemplateSelector to show my UserControl, the SortDescription is not gone and the ItemsSource ist not set to null on Unload. 如果我使用TemplateSelector来显示我的UserControl,则SortDescription不会消失,并且在Unload上ItemsSource不会设置为null。 the question is, why are these different behaviors? 问题是,为什么这些不同的行为? Is one on the 2 behaviors a bug? 这两个行为中的一个是一个bug吗?

btw. 顺便说一句。 I use .Net 4.5.1 but 4.6.1 is installed and system.Windows.Interactivity 4.0.0.0 我使用.Net 4.5.1但安装了4.6.1并且system.Windows.Interactivity 4.0.0.0

MainWindow.xaml 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 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 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 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 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;
    }
}
}

EDIT: i end up using this 编辑: 我最终使用这个

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


}

When you host your MyGridControl directly inside MyViewUc (case 1) - when you switch workspace and MyViewUC is unloaded, it's datacontext is set to null. 直接在MyViewUc中托管MyGridControl时(案例1) - 当您切换工作区并卸载MyViewUC时,它的datacontext设置为null。 Because MyGridControl is direct child - it's datacontext is set to null too, and in turn DataContext of DataGrid. 因为MyGridControl是直接子节点 - 它的datacontext也设置为null,而DataGrid则设置为DataContext。 This sets ItemsSource to null too, because it's bound to DataContext. 这也将ItemsSource设置为null,因为它绑定到DataContext。 You can verify this by looking at DataContext of DataGrid in your behavior. 您可以通过在行为中查看DataGrid的DataContext来验证这一点。 This behavior is completely reasonable to my mind. 这种行为在我看来是完全合理的。

When you use template selector: MyViewUC is unloaded, it's datacontext is set to null. 使用模板选择器时:卸载MyViewUC,将datacontext设置为null。 Then ContentControl Content is set to null, too. 然后ContentControl Content也设置为null。 Now here is the problem: when you use ContentTemplateSelector, DataContext of your old (unloaded) MyGridControl is NOT set to null. 现在问题是:当您使用ContentTemplateSelector时,旧的(卸载的)MyGridControl的DataContext不会设置为null。 You can verify this in your behaviour, that is why ItemsSource and sort descriptors are preserved. 您可以在行为中验证这一点,这就是保留ItemsSource和排序描述符的原因。

Now, I believe this second behaviour is not correct and datacontext should be set to null for this unloaded control created by ContentTemplateSelector. 现在,我认为第二种行为是不正确的,并且对于ContentTemplateSelector创建的这个卸载控件,应该将datacontext设置为null。 The logic behind this is not very simple - you can look yourself at source code of ContentPresenter.OnContentChanged method, where you will see when DataContext is not updated when content changes. 这背后的逻辑并不是很简单 - 您可以查看ContentPresenter.OnContentChanged方法的源代码,在这里您将看到内容更改时DataContext未更新的时间。

UPDATE: I see your main concern is losing sort descriptors, but that is direct consequence of losing DataContext and setting ItemsSource to null. 更新:我看到你主要担心的是丢失排序描述符,但这是丢失DataContext并将ItemsSource设置为null的直接后果。 To me this behaviour looks reasonable, but I see indeed for many people it is not, so that there is even bug report about this issue: https://connect.microsoft.com/VisualStudio/feedback/details/592897/collectionviewsource-sorting-only-the-first-time-it-is-bound-to-a-source 对我来说,这种行为看起来很合理,但我确实看到很多人确实没有,所以甚至有关于这个问题的bug报告: https//connect.microsoft.com/VisualStudio/feedback/details/592897/collectionviewsource-sorting -只最首次-IT-是绑定到一个源

You can see yourself in DataGrid source code that: 您可以在DataGrid源代码中看到自己:

protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
  base.OnItemsSourceChanged(oldValue, newValue);
  if (newValue == null)
    this.ClearSortDescriptionsOnItemsSourceChange();
  // more code here....
}

So when you set ItemsSource to null - all sort descriptors are explicitly cleared. 因此,当您将ItemsSource设置为null时,将显式清除所有排序描述符。 At link above you can find some workarounds which you might find useful. 在上面的链接中,您可以找到一些您可能会觉得有用的变通方法。

UPDATE2: you can consider trying to fix that behaviour by inheriting from DataGrid. UPDATE2:您可以考虑通过继承DataGrid来尝试修复该行为。 I don't say that is perfect solution, but neither is using ContentTemplateSelector. 我不是说这是完美的解决方案,但两者都没有使用ContentTemplateSelector。 There are two places where sort descriptors are cleared when ItemsSource is set to null - in OnItemsSourceChanged and OnCoerceItemsSourceProperty. 当ItemsSource设置为null时,有两个地方可以清除排序描述符 - 在OnItemsSourceChanged和OnCoerceItemsSourceProperty中。 So you can do the following: 所以你可以做到以下几点:

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

With code above you will see that sort descriptors are preserved in your MyView between switching datacontext. 使用上面的代码,您将看到在切换datacontext之间,MyView中保留了排序描述符。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM