View Issue Details

IDProjectCategoryView StatusLast Update
0004160NoesisGUIC++ SDKpublic2025-05-19 17:08
Reporteranton.sodergren_pdx Assigned Tosfernandez  
PrioritynormalSeverityminor 
Status resolvedResolutionfixed 
Product Version3.2.4 
Target Version3.2.8 
Summary0004160: Duplicate entries in ItemsControl when moved in and out of visual tree
Description

We discovered an interesting issue regarding ItemsControls and ObservableCollections. Particularly when an ItemsControl is moved in and out of the visual tree while items are added and removed from the observable collection.
We have a custom content control called VisibilityControl that takes in a ShouldDisplay bool as a dependency property. The control fills a similar purpose to just setting the Visibility property on a control, but also ensures that bindings are not resolved while the object is hidden. It can for example be used like this:

<jomini:VisibilityControl ShouldDisplay="{Binding MyCollection.Count, Converter={StaticResource Converter_LargerThanZero}}">
    <StackPanel>
        <TextBlock Text="{Binding MyCollection.Count}"/>
        <ItemsControl ItemsSource="{Binding MyCollection}">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding EntryName}"/>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </StackPanel>
</jomini:VisibilityControl>

While the ShouldDisplay value is true, Content property (inherited from ContentControl) is set to what is defined inside of the control. While the bool is false, the Content property will be empty. So the content is only inside the visual tree while the property is true. In the example, that is while the source collection is not empty. In the above example, just setting the Visibility property instead would have been fine, but there are some other cases where we have needed the VisibilityControl for the sake of performance and avoiding error messages.

The problem we're getting is the following:

  1. Let's say MyCollection initially contains 1 element, which means that the ItemsControl will be visible. We will see 1 element within the ItemsControl. The TextBlock will display 1.
  2. Then we clear MyCollection in code. Now neither the ItemsControl nor the TextBlock is visible.
  3. We add 1 new element to MyCollection. Now we should see 1 element in the ItemsControl, but for some reason 2 elements are shown, and they are duplicates of each other. The TextBlock will display 1.
  4. We add 1 new element to MyCollection. Now we should see 2 elements in the ItemsControl, but instead 3 elements are shown, and the first 2 are duplicates of each other. The TextBlock will display 2.

As you can see, the ObservableCollection contains the right number of elements, but the ItemsControl does not, instead it contains duplicates. So the ItemsControl essentially goes out of sync with the ObservableCollection. This does not happen if we don't wrap the ItemsControl in a VisibilityControl, so that is why I assume that it has something to do with moving the ItemsControl in and out of the visual tree. Perhaps it's handling the CollectionChanged callback incorrectly somehow when this happens?

PlatformAny

Activities

sfernandez

sfernandez

2025-05-15 17:23

manager   ~0010675

I was able to reproduce this scenario... working on a fix now.

sfernandez

sfernandez

2025-05-16 10:07

manager   ~0010679

Could you please try the following patch:

Index: ItemContainerGenerator.cpp
===================================================================
--- ItemContainerGenerator.cpp  (revision 14200)
+++ ItemContainerGenerator.cpp  (working copy)
@@ -835,6 +835,13 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 void ItemContainerGenerator::OnItemAdded(BaseComponent*, int itemIndex)
 {
+    int numItems = mHost != 0 ? mHost->GetItems()->Count() : 0;
+    if (numItems == (int)mNumItems)
+    {
+        // Generator already updated by another event
+        return;
+    }
+
     NS_ASSERT(itemIndex >= 0 && itemIndex <= (int)mNumItems);

     int index = -1;
@@ -888,6 +895,13 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 void ItemContainerGenerator::OnItemRemoved(BaseComponent* item, int itemIndex)
 {
+    int numItems = mHost != 0 ? mHost->GetItems()->Count() : 0;
+    if (numItems == (int)mNumItems)
+    {
+        // Generator already updated by another event
+        return;
+    }
+
     NS_ASSERT(itemIndex >= 0 && itemIndex <= (int)mNumItems);

     Ptr<DependencyObject> container;
anton.sodergren_pdx

anton.sodergren_pdx

2025-05-19 16:47

reporter   ~0010710

Hi! I tried applying the patch now, and it indeed solved the issue! Thanks! :D

sfernandez

sfernandez

2025-05-19 17:08

manager   ~0010714

Last edited: 2025-05-19 17:08

Great! This is solved in changeset r15608.

Issue History

Date Modified Username Field Change
2025-05-13 15:19 anton.sodergren_pdx New Issue
2025-05-13 18:25 sfernandez Assigned To => sfernandez
2025-05-13 18:25 sfernandez Status new => assigned
2025-05-13 18:25 sfernandez Target Version => 3.2.8
2025-05-13 18:25 sfernandez Description Updated
2025-05-15 17:23 sfernandez Status assigned => feedback
2025-05-15 17:23 sfernandez Note Added: 0010675
2025-05-16 10:07 sfernandez Note Added: 0010679
2025-05-19 16:47 anton.sodergren_pdx Note Added: 0010710
2025-05-19 16:47 anton.sodergren_pdx Status feedback => assigned
2025-05-19 17:08 sfernandez Status assigned => resolved
2025-05-19 17:08 sfernandez Resolution open => fixed
2025-05-19 17:08 sfernandez Note Added: 0010714
2025-05-19 17:08 sfernandez Note Edited: 0010714