嵌套的ICollectionView绑定会中断

后端开发 2026-07-07

我有一个数据模型,其中包含一个对象集合,每个对象又包含另一个对象的集合。具体来说,是一个权重集合,每个权重还包含一个权重密度的集合。当我绑定ObservableCollections时它们工作正常,但绑定嵌套的ICollectionView时,无论父级绑定是ObservableCollection还是ICollectionView,嵌套的ICollectionView都会在SourceList的 ListCollectionView.CanAddNew处抛出“Object reference not set to an instance of an object.”的错误。请注意,开启调试跟踪时,绑定到ObsverableCollection(工作)和ICollectionView(失败)的日志完全一致。并且调用栈只包含在XAML处理过程深处的函数调用,无法在这些处设置断点。

以下是相关代码。我出于篇幅原因没有添加ObservableObject类,所有从事WPF和 MVVM的人可能都已经有实现了INotifyPropertyChanged的自定义ObservableObject类。如果需要,我可以添加。

进一步复杂化的是,在WeightsViewModel构造函数中,如果在设置ICollectionView之前调用InitializeWeights,第一次绑定的ICollectionView能正确绑定,因为数据网格初始化时生效,但之后再也不行。如果在设置ICollectionView之后再调用它,即使在第一次尝试时也会失败。

另外,我还查看了 WPF - Datagrid ICollectionView binding 等类似的资料,并据我所能理解实现了这些建议。

感谢关注。

数据类定义

  public class WeightDensity : ObservableObject
  {
    double _value = 0;
    public double Value { get { return _value; } set { Set(ref _value, value); } }

        public WeightDensity() { }
        public WeightDensity(double value)
        { this.Value = value; }
    }
  public class WeightItem : ObservableObject
  {
    string _name = "";
    public string Name { get { return _name; } set { Set(ref _name, value); } }

        double _value = 0;
        public double Value { get { return _value; } set { Set(ref _value, value); } }

        public ObservableCollection<WeightDensity> _distributedWeights;
        public ObservableCollection<WeightDensity> DistributedWeights
        {
            get => _distributedWeights;
            set { _distributedWeights = value; RaisePropertyChanged(nameof(DistributedWeights)); }
        }

        ICollectionView _distributedWeightsView;
        public ICollectionView DistributedWeightsView
        {
            get => _distributedWeightsView;
            set { _distributedWeightsView = value; RaisePropertyChanged(nameof(DistributedWeightsView)); }
        }

        public WeightItem() : this("") { }
        public WeightItem(string name)
        {
            _name = name;
            _distributedWeights = [];
            CollectionViewSource cvs = new()
            {
                Source = DistributedWeights
            };
            DistributedWeightsView = CollectionViewSource.GetDefaultView(cvs.View);
        }
    }

模型

  class Model
  {
        public ObservableCollection<WeightItem> Weights { get; private set; } = [];

        public void InitializeWeights()
        {
            WeightItem wi = new("AFT")
            {
                Value = 100,
            };
            wi.DistributedWeights.Add(new(10));
            wi.DistributedWeights.Add(new(20));
            Weights.Add(wi);

            wi = new("FWD")
            {
                Value = 200,
            };
            wi.DistributedWeights.Add(new(30));
            wi.DistributedWeights.Add(new(50));
            Weights.Add(wi);
        }
    }

模型视图

    internal class WeightsViewModel : ObservableObject
    {
        readonly Model _model = new();

        //private WeightCollection _weights = null;
        private ObservableCollection<WeightItem> _weights;
        public ObservableCollection<WeightItem> Weights
        {
            get { return _weights; }
            set { _weights = value; RaisePropertyChanged(nameof(Weights)); }
        }
        private ICollectionView _weightsView { get; set; }
        public ICollectionView WeightsView
        {
            get => _weightsView;
            set { _weightsView = value; RaisePropertyChanged(nameof(WeightsView)); }
        }

        public WeightsViewModel()
        {
            _model.InitializeWeights();
            _weights = _model.Weights;
            CollectionViewSource cvs = new()
            {
                Source = _weights
            };
            WeightsView = CollectionViewSource.GetDefaultView(cvs.View);
        }
    }

XAML

<Window x:Class="WpfNET8Test.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:WpfNET8Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">

    <Window.DataContext>
        <local:WeightsViewModel />
    </Window.DataContext>

    <Grid>
        <DataGrid x:Name="dgWeights" ItemsSource="{Binding WeightsView}" AutoGenerateColumns="False"
                  CanUserSortColumns="False" CanUserResizeRows="False" SelectionMode="Single">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=OneWay}" />
                <DataGridTextColumn Header="Weight" Binding="{Binding Value, Mode=OneWay}" />
            </DataGrid.Columns>

            <!-- DistributedWeights -->
            <DataGrid.RowDetailsTemplate>
                <DataTemplate x:Name="WeightItemRowDetails">
                    <StackPanel Orientation="Vertical">
                        <DataGrid x:Name="dgDistroWeights" ItemsSource="{Binding DistributedWeightsView, Mode=OneWay}" AutoGenerateColumns="False" 
                                  CanUserSortColumns="False" CanUserResizeRows="False" SelectionMode="Single">
                            <DataGrid.Columns>
                                <DataGridTextColumn Header="Weight" Binding="{Binding Value, Mode=OneWay}" />
                            </DataGrid.Columns>
                        </DataGrid>
                    </StackPanel>
                </DataTemplate>
            </DataGrid.RowDetailsTemplate>

        </DataGrid>
    </Grid>
</Window>

解决方案

经过一些调查,我找到了解决方案。它并不完美,但我似乎找不到可替代的方法。问题出在 WeightItem 及其ICollectionView上,当你在构造函数中创建它时,绑定访问时源列表似乎会丢失。但是在访问属性时再去构造,它就能正常工作。因此你需要对代码做出一些修改,下面是一个已经准备好的、具有可工作的 ICollectionView 的类:

    public class WeightItem : ObservableObject
    {
        string _name = "";
        public string Name { get { return _name; } set { _name = value; OnPropertyChanged(); } }

        double _value = 0;
        public double Value { get { return _value; } set { _value = value; OnPropertyChanged(); } }

        public ObservableCollection<WeightDensity> _distributedWeights;
        public ObservableCollection<WeightDensity> DistributedWeights
        {
            get => _distributedWeights;
            set { _distributedWeights = value; OnPropertyChanged(); }
        }

        ICollectionView _distributedWeightsView;
        public ICollectionView DistributedWeightsView
        {
            get
            {
                CollectionViewSource cvs = new()
                {
                    Source = DistributedWeights
                };

                return CollectionViewSource.GetDefaultView(cvs.View);//creating view every time works without any issues
            }
            set { _distributedWeightsView = value; OnPropertyChanged(); }
        }

        public WeightItem() : this("") { }
        public WeightItem(string name)
        {
            _name = name;
            _distributedWeights = [];//and constructor looks cleaner too
        }
    }
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章