Description
Describe the bug
In certain situations, the child element of an adorner layer is drawn in an incorrect location. This seems to occur in scenarios where a VisualLayerManager is detached and reattached to the visual tree (such as using a VisualLayerManager inside a TabControl).
To Reproduce
<TabControl>
<TabItem Header="Tab 1">
<VisualLayerManager>
<ListBox>
<ListBoxItem>
<TextBlock Text="Item 1"/>
</ListBoxItem>
<ListBoxItem>
<TextBlock Text="Item 2"/>
</ListBoxItem>
<ListBoxItem>
<TextBlock Text="Item 3"/>
</ListBoxItem>
<ListBoxItem>
<Grid>
<AdornerLayer.Adorner>
<TextBlock Background="Gray" HorizontalAlignment="Center">
This should be drawn at the last item of the listbox
</TextBlock>
</AdornerLayer.Adorner>
<TextBlock Text="Item 4"/>
</Grid>
</ListBoxItem>
</ListBox>
</VisualLayerManager>
</TabItem>
<TabItem Header="Tab 2">
<TextBlock>Tab 2</TextBlock>
</TabItem>
</TabControl>
Upon initial load, the adorner in the first tab is drawn in the correct location:
If you select the second tab (thus detaching the adorner layer from the visual tree) and then go back to the first, the adorner is drawn at the top of the visual layer manager:
The adorner also no longer scrolls with the list item (only visible if the list is long enough to scroll)
Repro project attached below
TestApp.zip
Expected behavior
Adorners should always be drawn in the correct location.
Avalonia version
11.2.8 (also tested with build from master branch)
OS
Windows
Additional context
I believe this is a result of how VisualOnAttachedToVisualTree
is implemented in AdornerLayer.cs.
AdornerLayer.AttachedToVisualTree
event is invoked- In the handler for the above event,
Attach
is called for the child of the layer
- The adorner is added to the
Children
of the AdornerLayer ChildrenCollectionChanged
event is fired forAdornerLayer.Children
UpdateAdornedElement
is called for the adorneradorner.CompositionVisual
isnull
, soadorner.CompositionVisual.AdornedVisual
is not set. (norAdornerIsClipped
)
- Then
OnAttachedToVisualTreeCore
is called for each visual child of the adorner layer
I think that since step 6 happens before step 7, things behave unexpectedly.
Proof of concept fix
As a proof of concept, I altered the implementation of ChildrenCollectionChanged
in AdornerLayer.cs and the adorner is now drawn correctly inside a tab control.
< removedUpdateAdornedElement(i, i.GetValue(AdornedElementProperty));
added:
if (!i.IsAttachedToVisualTree)
{
i.AttachedToVisualTree += (s, e) => { UpdateAdornedElement(i, i.GetValue(AdornedElementProperty)); };
}
else
{
UpdateAdornedElement(i, i.GetValue(AdornedElementProperty));
}
But adding an event handler doesn't seem like the correct approach for an actual fix.
While trying to create a simple repro for this, I also noticed another possibly related issue:
If you write the following xaml, the adorner is drawn at the top of the stack panel even on first load. If you then navigate to a different tab and back to the first, an exception is thrown.
<TabControl>
<TabItem Header="Tab 1">
<VisualLayerManager>
<StackPanel>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
<Grid>
<AdornerLayer.Adorner>
<TextBlock Background="Gray" HorizontalAlignment="Center">
This should be drawn at the last item of the stack panel
</TextBlock>
</AdornerLayer.Adorner>
<TextBlock Text="Item 4"/>
</Grid>
</StackPanel>
</VisualLayerManager>
</TabItem>
<TabItem Header="Tab 2">
<TextBlock>Tab 2</TextBlock>
</TabItem>
</TabControl>
I didn't attempt any debugging for this part, but this is also reproduced in the attached zip.
This is the exception:
Finally,
This might be related. I don't really know, but I see "adorner" and "compositor" in the title, so...maybe? 😅