bli*_*eis 4 c# wpf mvvm icollectionview
这就是我想要的:如果我将 ICollectionview 绑定到 DataGrid,我不想丢失 Viewmodel 中的 SortDescription。
\n\n我创建了一个小示例项目来看看我的意思。在我的项目中,我只是使用用户控件在数据网格中显示我的数据。如果我这样做,当 UserControl 卸载时, SortDescription 就会消失,因为 ItemsSource 设置为 null。如果我使用 TemplateSelector 显示我的 UserControl,则 SortDescription不会消失,并且 ItemsSource 在卸载时不会设置为 null。问题是,为什么会有这些不同的行为?这两种行为中的一种是错误吗?
\n\n顺便提一句。我使用 .Net 4.5.1 但安装了 4.6.1 和 system.Windows.Interactivity 4.0.0.0
\n\n主窗口.xaml
\n\n<Window x:Class="DataGridICollectionView.MainWindow"\n xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\n xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\n xmlns:d="http://schemas.microsoft.com/expression/blend/2008"\n xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"\n xmlns:local="clr-namespace:DataGridICollectionView"\n xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"\n mc:Ignorable="d"\n Title="MainWindow" Height="350" Width="525">\n<Window.Resources>\n <DataTemplate DataType="{x:Type local:ViewmodelListe}">\n <local:MyViewUc/>\n </DataTemplate>\n</Window.Resources>\n<Grid>\n <Grid.RowDefinitions>\n <RowDefinition Height="Auto"/>\n <RowDefinition Height="Auto"/>\n </Grid.RowDefinitions>\n\n <ToolBar Grid.Row="0">\n <Button Content="SetWorkspace MyView" Click="Button_Click"/>\n <Button Content="SetWorkspace Other" Click="Button_Click_1"/>\n </ToolBar>\n\n <ContentPresenter Grid.Row="1" Content="{Binding Workspace}"/>\n</Grid>\n</Window>\nRun Code Online (Sandbox Code Playgroud)\n\nMainWindow.xaml.cs
\n\nnamespace DataGridICollectionView\n{\n/// <summary>\n/// Interaktionslogik f\xc3\xbcr MainWindow.xaml\n/// </summary>\npublic partial class MainWindow : Window, INotifyPropertyChanged\n{\n private object _workspace;\n\n public MainWindow()\n {\n InitializeComponent();\n MyViewVm = new ViewmodelListe();\n\n DataContext = this;\n }\n\n public ViewmodelListe MyViewVm { get; set; }\n\n public object Workspace\n {\n get { return _workspace; }\n set\n {\n _workspace = value;\n OnPropertyChanged();\n }\n }\n\n private void Button_Click(object sender, RoutedEventArgs e)\n {\n Workspace = MyViewVm;\n }\n\n private void Button_Click_1(object sender, RoutedEventArgs e)\n {\n Workspace = "Other";\n }\n\n public event PropertyChangedEventHandler PropertyChanged;\n\n protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n {\n PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n }\n}\n\npublic class ViewmodelListe : INotifyPropertyChanged\n{\n public ViewmodelListe()\n {\n Persons = new ObservableCollection<Person>();\n MyView = CollectionViewSource.GetDefaultView(Persons);\n\n Persons.Add(new Person() {FirstName = "P1", LastName = "L1"});\n Persons.Add(new Person() {FirstName = "P2", LastName = "L2"});\n Persons.Add(new Person() {FirstName = "P3", LastName = "L3"});\n }\n\n public ObservableCollection<Person> Persons { get; private set; }\n\n public ICollectionView MyView { get; private set; } \n\n public event PropertyChangedEventHandler PropertyChanged;\n\n protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n {\n PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n }\n}\n\npublic class Person : INotifyPropertyChanged\n{\n private string _firstName;\n private string _lastName;\n\n public string FirstName\n {\n get { return _firstName; }\n set\n {\n _firstName = value; \n OnPropertyChanged();\n }\n }\n\n public string LastName\n {\n get { return _lastName; }\n set\n {\n _lastName = value;\n OnPropertyChanged();\n }\n }\n\n public event PropertyChangedEventHandler PropertyChanged;\n\n protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)\n {\n PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));\n }\n}\n\npublic class TestBehavior : Behavior<DataGrid>\n{\n protected override void OnAttached()\n {\n base.OnAttached();\n AssociatedObject.Unloaded += AssociatedObjectUnloaded;\n }\n\n private void AssociatedObjectUnloaded(object sender, RoutedEventArgs e)\n {\n //look at this in Debug Mode, its NULL if you dont use the TemplateSelector\n var itemssource = AssociatedObject.ItemsSource;\n\n\n }\n\n protected override void OnDetaching()\n {\n base.OnDetaching();\n AssociatedObject.Unloaded -= AssociatedObjectUnloaded;\n }\n}\n}\nRun Code Online (Sandbox Code Playgroud)\n\nMyGridControl.xaml
\n\n<UserControl x:Class="DataGridICollectionView.MyGridControl"\n xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\n xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\n xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" \n xmlns:d="http://schemas.microsoft.com/expression/blend/2008" \n xmlns:local="clr-namespace:DataGridICollectionView"\n xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"\n mc:Ignorable="d" \n d:DesignHeight="300" d:DesignWidth="300">\n<Grid>\n <DataGrid ItemsSource="{Binding MyView}" AutoGenerateColumns="True">\n <i:Interaction.Behaviors>\n <local:TestBehavior/>\n </i:Interaction.Behaviors>\n </DataGrid>\n</Grid>\n</UserControl>\nRun Code Online (Sandbox Code Playgroud)\n\nMyViewUc.xaml
\n\n<UserControl x:Class="DataGridICollectionView.MyViewUc"\n xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"\n xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"\n xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" \n xmlns:d="http://schemas.microsoft.com/expression/blend/2008" \n xmlns:local="clr-namespace:DataGridICollectionView"\n xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"\n mc:Ignorable="d" \n d:DesignHeight="300" d:DesignWidth="300">\n<UserControl.Resources>\n <DataTemplate x:Key="MyViewCrap">\n <local:MyGridControl/>\n </DataTemplate>\n\n <local:MyTemplateSelector x:Key="Selector" GridView="{StaticResource MyViewCrap}" />\n</UserControl.Resources>\n<Grid>\n <!--When using Contentcontrol with TemplateSelector- ItemsSource is NOT set to null -->\n <ContentControl Content="{Binding .}" ContentTemplateSelector="{StaticResource Selector}"/>\n <!--When using MyGridControl withOUT TemplateSelector- ItemsSource is set to NULL -->\n <!--<local:MyGridControl/>-->\n</Grid>\n</UserControl>\nRun Code Online (Sandbox Code Playgroud)\n\nMyViewUc.xaml.cs
\n\nnamespace DataGridICollectionView\n{\n/// <summary>\n/// Interaktionslogik f\xc3\xbcr MyViewUc.xaml\n/// </summary>\npublic partial class MyViewUc : UserControl\n{\n public MyViewUc()\n {\n InitializeComponent();\n }\n}\n\npublic class MyTemplateSelector : DataTemplateSelector\n{\n public DataTemplate GridView { get; set; }\n\n\n public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)\n {\n var chooser = item as ViewmodelListe;\n if (chooser == null)\n {\n return base.SelectTemplate(item, container);\n }\n\n return GridView;\n }\n}\n}\nRun Code Online (Sandbox Code Playgroud)\n\n编辑:我最终使用这个
\n\npublic class MyDataGrid : DataGrid\n{\n\n static MyDataGrid ()\n {\n ItemsSourceProperty.OverrideMetadata(typeof(MyDataGrid ),new FrameworkPropertyMetadata(null, OnPropertyChangedCallBack, OnCoerceItemsSourceProperty));\n }\n\n private ICollectionView _defaultView;\n protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)\n {\n if(_defaultView != null)\n _defaultView.CollectionChanged -= LiveSortingPropertiesOnCollectionChanged;\n\n base.OnItemsSourceChanged(oldValue, newValue);\n\n _defaultView = newValue as ICollectionView;\n if(_defaultView != null)\n _defaultView.CollectionChanged += LiveSortingPropertiesOnCollectionChanged;\n }\n\n private void LiveSortingPropertiesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)\n {\n if (e.Action == NotifyCollectionChangedAction.Reset)\n {\n foreach (var dataGridColumn in this.Columns)\n {\n var isSortDirectionSetFromCollectionView = false;\n foreach (var sortDescription in _defaultView.SortDescriptions)\n {\n if (dataGridColumn.SortMemberPath == sortDescription.PropertyName)\n {\n dataGridColumn.SortDirection = sortDescription.Direction;\n isSortDirectionSetFromCollectionView = true;\n break;\n }\n }\n\n if (!isSortDirectionSetFromCollectionView)\n {\n dataGridColumn.SortDirection = null;\n }\n }\n }\n }\n\n private static void OnPropertyChangedCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)\n {\n var grd = d as MyDataGrid ;\n var view = e.NewValue as ICollectionView;\n\n if (grd == null || view == null)\n return;\n\n foreach (var dataGridColumn in grd.Columns)\n {\n var isSortDirectionSetFromCollectionView = false;\n foreach (var sortDescription in view.SortDescriptions)\n {\n if (dataGridColumn.SortMemberPath == sortDescription.PropertyName)\n {\n dataGridColumn.SortDirection = sortDescription.Direction;\n isSortDirectionSetFromCollectionView = true;\n break;\n }\n }\n //wenn die View nicht sortiert war, auch die column nicht Sortieren\n if (!isSortDirectionSetFromCollectionView)\n {\n dataGridColumn.SortDirection = null;\n }\n }\n }\n\n\n private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)\n {\n // do nothing here - we just want to override parent behaviour.\n // The _only_ thing DataGrid does here is clearing sort descriptors\n return baseValue;\n }\n\n\n}\nRun Code Online (Sandbox Code Playgroud)\n
当您直接在 MyViewUc 中托管 MyGridControl 时(情况 1) - 当您切换工作区并且 MyViewUC 被卸载时,它的数据上下文设置为 null。因为 MyGridControl 是直接子级 - 它的 datacontext 也设置为 null,而 DataGrid 的 DataContext 也设置为 null。这也将 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 的直接后果。对我来说,这种行为看起来很合理,但我确实发现对于很多人来说事实并非如此,因此甚至有关于此问题的错误报告: https: //connect.microsoft.com/VisualStudio/feedback/details/592897/collectionviewsource-sorting -仅第一次绑定到源
您可以在 DataGrid 源代码中看到:
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (newValue == null)
this.ClearSortDescriptionsOnItemsSourceChange();
// more code here....
}
Run Code Online (Sandbox Code Playgroud)
因此,当您将 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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
通过上面的代码,您将看到排序描述符在切换数据上下文之间保留在 MyView 中。
| 归档时间: |
|
| 查看次数: |
858 次 |
| 最近记录: |