如何安全地替换用作 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));
}
}
问题:这是导致神秘异常的事件序列:
- 用户点击“删除”按钮。
DeleteCarMake()
通过回调调用。
DeleteCarMake()
删除选定的 CarMake
并完全重置 Cars
ObservableCollection
. 的内容
- 下次用户点击
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
中为 TreeView
的 SelectionChanged
事件定义事件处理程序那么异常永远不会发生。我不太确定这意味着什么,但希望其他人可以拼凑出拼图的各个部分。
这似乎是一个计划在 WinUI 3 的下一个预览版中修复的错误。来源 here。
我正在编写 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));
}
}
问题:这是导致神秘异常的事件序列:
- 用户点击“删除”按钮。
DeleteCarMake()
通过回调调用。DeleteCarMake()
删除选定的CarMake
并完全重置Cars
ObservableCollection
. 的内容
- 下次用户点击
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
中为 TreeView
的 SelectionChanged
事件定义事件处理程序那么异常永远不会发生。我不太确定这意味着什么,但希望其他人可以拼凑出拼图的各个部分。
这似乎是一个计划在 WinUI 3 的下一个预览版中修复的错误。来源 here。