-
Notifications
You must be signed in to change notification settings - Fork 706
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix TreeView missing children issue #2058
Fix TreeView missing children issue #2058
Conversation
I was looking into this exact issue yesterday, fun timing :) |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
@Felix-Dev the test failure looks like its a real issue to me, are you able to look into it? |
@StephenLPeters I'm seeing the test failures locally too. I will look at them this weekend and see if there is something I can do. Edit: Here's the setup before the crash:
If we now collapse and then expand "Root" the app crashes. It doesn't matter if "Root.1" is expanded or not. As long as "Root.0" is expanded when we collapse "Root" the crash will occur when subsequently epxanding "Root" again. Edit2 @kaiguo: Note that in scenario a) the underlying treenode of "Root.2" is not expanded. In scenario b) however, the underlying treenode is expanded (why is that?). Thus, the logic executed in both cases might differ. I added additional test code which you can see in the pics above:
This sets the underlying treenode for "Root.2" to not expanded and scenario b) now seems to follow the code path of scenario a) as a result. However, with or without this line, I still get the same crash which happens sometimes after TreeViewItemSource.HasUnrealizedChildren for "Root.0" is requested (using the debugger, the code crashes as the next step after TreeViewPage.g.cs::Update_HasUnrealizedChildren is called). The next step in code execution would be TreeViewList::OnContainerContentChanging() for "Root.1" (based on scenario a). Unfortunately, something is causing the crash prior to that code execution in scenario b) and all I'm seeing for now is a bunch of Windows.Ui.Xaml.dll calls I cannot inspect. It looks like the issue is releated to "Root.0" and its children and not something related to "Root.2". I'm not sure if I can debug this code further and find the root cause on my own. |
@kaiguo thoughts on @Felix-Dev s comment? |
@Felix-Dev I think It's because you are trying to update ListView ItemsSource during layout pass. The node expansion operation is basically adding all the children into Listview ItemsSource which triggers a layout pass and calls ContainerContantChanging, and inside ContainerContantChanging ItemsSource is being set again, it'll trigger another layout pass. @ranjeshj are there other places we can safely put the ItemsSource updating code? |
Please avoid updating ItemsSource (or changing the collection in any way) within layout. Doing so will likely end up causing issues down the line. You can set it through a dispatcher call though which will run after layout completes. |
@ranjeshj @kaiguo So I added a dispatcher call (looked at TreeViewList::PrepareContainerForItemOverride) and it added a bunch of new problems: if (auto itemsSource = targetItem.ItemsSource())
{
const DispatcherHelper dispatcher{ *this };
dispatcher.RunAsync(
[treeViewItem, targetNode, itemsSource]()
{
treeViewItem->SetItemsSource(targetNode, itemsSource);
});
} See the following image for the failing test case for example (collapse Root with both Root.0 and Root.1 collapsed, then expand): If I now expand Root.2 then collapse Root and expand Root again, the app crashes as in the previous cases (only Root.0 substituted with Root.2). Ignore the dispatcher code for now, I added another check to the code which seems to fix the crash and failing tests just fine: if (auto itemsSource = targetItem.ItemsSource())
{
if (treeViewNode->ItemsSource() == nullptr)
{
treeViewItem->SetItemsSource(targetNode, itemsSource);
}
} This is based on the observation that the treeview issues this PR attempts to fix really only happened because Since this check fixes the issues I then wrapped the ItemsSource update inside a dispatcher call, like so: if (auto itemsSource = targetItem.ItemsSource())
{
if (treeViewNode->ItemsSource() == nullptr)
{
const DispatcherHelper dispatcher{ *this };
dispatcher.RunAsync(
[treeViewItem, targetNode, itemsSource]()
{
treeViewItem->SetItemsSource(targetNode, itemsSource);
});
}
} That, however, caused three new tests to fail: ItemsSourceResycTest, TreeViewRootNodeBindingTest and TreeViewWithMultiLevelChildrenExpandCollapseTest. I decided to push the new check for now as that makes all my local tests run fine. If the tests run fine here too, we can see how to put that in a dispatcher call if still required so that no new test failures will occur. |
/azp run |
Azure Pipelines successfully started running 1 pipeline(s). |
If null check fixes the problem I think we should go with the null check approach since dispatcher call makes the code hard to debug and understand, but I'll defer to @ranjeshj to make the call : ) |
Ok. The null check sounds reasonable and we can revisit if this becomes an issue. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Description
With data bound TreeView items some recycled item containers (TreeViewItem) are losing their bound children collection. Futher debugging the code shows that the TreeView nodes of such TreeViewItems also have lost their children (TreeViewNode.m_itemsDataSource is null). As such, calling TreeViewNode.SyncChildrenNodesWithItemsSource() is not fixing this issue.
The current fix is a first attempt at fixing the issue. It forces a binding re-evaluation of the TreeViewItem.ItemsSource dependency property. As such, TreeViewItem.OnPropertyChanged is called which in return sets the items source of the corresponding TreeViewNode (TreeViewNode.ItemsSource(itemsSource)).
As we are basically doing the steps after TreeView creation again, there might be a better way to fix the issue. I would like to ask a team help aid me in search for a better fix in case there is one 🙂
Motivation and Context
Fixes #1701 and Fixes #523.
How Has This Been Tested?
Added test to the MUXControlsTestApp TreeView test page. Test can be modified if requested.