嵌套的ICollectionView绑定会中断
我有一个数据模型,其中包含一个对象集合,每个对象又包含另一个对象的集合。具体来说,是一个权重集合,每个权重还包含一个权重密度的集合。当我绑定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
}
}