0

I'm writing WinUI 3 desktop application. The main window consists of a TreeView with the names of car makes as the parent items, and car models as the children. Here's the relevant code:

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));
        }
    }

The problem: Here's the sequence of events that leads to the mystery exception:

  1. The user clicks the "Delete" button.
  2. DeleteCarMake() is invoked via callback.
  3. DeleteCarMake() deletes the selected CarMake and completely resets the contents of the Cars ObservableCollection.
  4. The next time the user clicks on the TreeView an exception is thrown.

The problem seems to be connected to the complete removal of the contents of Cars and subsequent copying of data back into Cars (note that Cars serves as the ItemsSource for the TreeView). If I simply remove the carMakeToDelete from Cars, the exception isn't thrown next time the user clicks the TreeView. However, for reasons which my simplified code can't express, I need to be able to completely Clear() Cars' contents at every delete, not just remove a single item.

The full call stack is here but as best I can tell the relevant part seems to be in the middle:

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

What I'd like to know: How can I safely Clear() the contents of my Cars ObservableCollection without causing an exception to be thrown the next time a user clicks on the TreeView?

One possible clue: If I don't define an event handler for the TreeView's SelectionChanged event in my MainWindow.xaml then the exception never occurs. I'm not quite sure what this implies, but hopefully someone else can put together the pieces of the puzzle.

8
  • The WinUI team is actively answering WinUI questions in WinUI GitHub(github.com/microsoft/microsoft-ui-xaml/issues). You could try to submit your questions there. Commented Feb 1, 2021 at 7:09
  • Thanks, I’ll probably do that if I can’t get an answer on SO. Commented Feb 1, 2021 at 14:24
  • Why are you clearing and then repopulating the observable collection? Why don't you simply remove the one item that needs to be removed? Commented Feb 1, 2021 at 23:59
  • @slugster I considered whether I should add a note to explain that: this isn't my "real" code. It's a minimal reproducible example that I created purely for the purpose of highlighting my issue without any of the extraneous noise that comes with my actual codebase. Trust me when I say if you could see my original code it'd be clear why I have to clear the collection. It's non-negotiable: I can't simply remove one item, I have to clear out and repopulate the whole thing every time. Commented Feb 2, 2021 at 0:23
  • 1
    Fair enough. One option then is to assign a new ObervableCollection to the Cars property rather then resetting and repopulating the existing list, of course Cars would need to notify of property change. I have seen UIs (third party controls) struggle or crash in the past due to the way people have updated a bound ObservableCollection, sometimes it is more efficient to create a new one. Of course these comments do not necessarily solve your particular problem, hence why they're comments and not an answer :) Commented Feb 2, 2021 at 0:29

1 Answer 1

0

This appears to be a bug slated to be fixed in the next preview release of WinUI 3. Source here.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.