如何安全地替换用作 TreeView 的 ItemsSource 的 ObservableCollection 的内容?

How can I safely replace the contents of an ObservableCollection that's serving as a TreeView's ItemsSource?

我正在编写 WinUI 3 桌面应用程序。主要 window 由一个 TreeView 组成,其中汽车品牌名称作为父项,汽车型号作为子项。这是相关代码:

MainWindow.xaml:

<Window
    x:Class="Cars.View.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:model="using:Cars.Model"
    xmlns:view="using:Cars.View"
    xmlns:utility="using:Cars.View.Utility">
    
    <Grid>
        <Grid.Resources>
                        
            <DataTemplate x:Key="CarMakeTemplate" x:DataType="model:CarMake">
                <TreeViewItem ItemsSource="{x:Bind Path=CarModels, Mode=OneWay}">
                    <StackPanel Orientation="Horizontal">
                        <view:CarMakeView CarMake="{x:Bind Mode=OneWay}"/>
                        <Button Content="Delete" Click="DeleteCarMake"/>
                    </StackPanel>
                </TreeViewItem>
            </DataTemplate>
            
            <DataTemplate x:Key="CarModelTemplate" x:DataType="model:CarModel">
                <TreeViewItem>
                    <view:CarModelView CarModel="{x:Bind Mode=OneWay}"/>
                </TreeViewItem>
            </DataTemplate>
            
            <utility:CarItemSelector
                x:Key="CarItemSelector"
                CarMakeTemplate="{StaticResource CarMakeTemplate}"
                CarModelTemplate="{StaticResource CarModelTemplate}" />
        </Grid.Resources>
        
        <TreeView ItemsSource="{x:Bind Cars, Mode=OneWay}"  
                  ItemTemplateSelector="{StaticResource CarItemSelector}"
                  SelectionChanged="HandleSelectedCarMakeChanged">
        </TreeView>
    </Grid>
    
</Window>

MainWindow.cs:

public sealed partial class MainWindow : Window, INotifyPropertyChanged
    {
        public ObservableCollection<CarMake> Cars { get; set; } =
            new ()
            {
                new CarMake { Name = "Chevrolet", CarModels = { new CarModel { Name = "Camaro" }, new CarModel { Name = "Blazer" }, new CarModel { Name = "Beretta" } } },
                new CarMake { Name = "Land Rover", CarModels = { new CarModel { Name = "Discovery" }, new CarModel { Name = "LR3" }, new CarModel { Name = "Range Rover" } } },
                new CarMake { Name = "Quadra", CarModels = { new CarModel { Name = "Turbo-R 740" }, new CarModel { Name = "Type-66 Avenger" }} },
                new CarMake { Name = "Powell Motors", CarModels = { new CarModel { Name = "The Homer" }}}
            };

        public MainWindow()
        {
            this.InitializeComponent();
        }

        private void DeleteCarMake(object sender, RoutedEventArgs eventInfo)
        {
            var carMakeToDelete = ((Button) eventInfo.OriginalSource).DataContext as CarMake;
            var updatedCarMakes = new List<CarMake>(Cars);
            updatedCarMakes.Remove(carMakeToDelete!);
            
            Cars.Clear();
            foreach (CarMake carMake in updatedCarMakes)
            {
                Cars.Add(carMake);
            }
            OnPropertyChanged(nameof(Cars));
        }

        private void HandleSelectedCarMakeChanged(TreeView sender, TreeViewSelectionChangedEventArgs info)
        {
            //do stuff
        }
        
        public event PropertyChangedEventHandler? PropertyChanged;

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

问题:这是导致神秘异常的事件序列:

  1. 用户点击“删除”按钮。
  2. DeleteCarMake() 通过回调调用。
  3. DeleteCarMake() 删除选定的 CarMake 并完全重置 Cars ObservableCollection.
  4. 的内容
  5. 下次用户点击 TreeView 时会抛出异常。

问题似乎与完全删除 Cars 的内容以及随后将数据复制回 Cars 有关(注意 Cars 作为 ItemsSource 代表 TreeView)。如果我简单地从 Cars 中删除 carMakeToDelete,则下次用户单击 TreeView 时不会抛出异常。但是,由于我的简化代码无法表达的原因,我需要能够在每次删除时完全 Clear() Cars' 内容,而不仅仅是删除单个项目。

完整的调用堆栈是 here,但据我所知,相关部分似乎在中间:

Microsoft.UI.Xaml.Controls.dll!ViewModel::UpdateNodeSelection(struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode const &,enum TreeViewNode::TreeNodeSelectionState const &)  Unknown
Microsoft.UI.Xaml.Controls.dll!ViewModel::UpdateSelection(struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode const &,enum TreeViewNode::TreeNodeSelectionState const &)  Unknown
Microsoft.UI.Xaml.Controls.dll!SelectedTreeNodeVector::UpdateSelection(struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode const &,enum TreeViewNode::TreeNodeSelectionState) Unknown
Microsoft.UI.Xaml.Controls.dll!SelectedTreeNodeVector::RemoveAt(unsigned int)   Unknown
Microsoft.UI.Xaml.Controls.dll!SelectedTreeNodeVector::Clear(void)  Unknown
Microsoft.UI.Xaml.Controls.dll!winrt::impl::produce<class SelectedTreeNodeVector,struct winrt::Windows::Foundation::Collections::IVector<struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode> >::Clear(void)  Unknown
Microsoft.UI.Xaml.Controls.dll!winrt::impl::consume_Windows_Foundation_Collections_IVector<struct winrt::Windows::Foundation::Collections::IVector<struct winrt::Microsoft::UI::Xaml::DependencyObject>,struct winrt::Microsoft::UI::Xaml::DependencyObject>::Clear(void)   Unknown
Microsoft.UI.Xaml.Controls.dll!ViewModel::SelectNode(struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode const &,bool)    Unknown
Microsoft.UI.Xaml.Controls.dll!TreeView::UpdateSelection(struct winrt::Microsoft::UI::Xaml::Controls::TreeViewNode const &,bool)    Unknown
Microsoft.UI.Xaml.Controls.dll!TreeViewItem::UpdateSelection(bool)  Unknown
Microsoft.UI.Xaml.Controls.dll!TreeViewItem::OnIsSelectedChanged(struct winrt::Microsoft::UI::Xaml::DependencyObject const &,struct winrt::Microsoft::UI::Xaml::DependencyProperty const &) Unknown

我想知道的:我怎样才能安全地 Clear() 我的 Cars ObservableCollection 的内容而不引起异常下次用户点击 TreeView?

时抛出

一条可能的线索:如果我没有在 MainWindow.xaml 中为 TreeViewSelectionChanged 事件定义事件处理程序那么异常永远不会发生。我不太确定这意味着什么,但希望其他人可以拼凑出拼图的各个部分。

这似乎是一个计划在 WinUI 3 的下一个预览版中修复的错误。来源 here