Skip to content

Adorner of AdornerLayer is attached to the layer before being attached to visual tree #18734

Open
@H0tCh0colat3

Description

@H0tCh0colat3

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:
Image
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:
Image
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.

  1. AdornerLayer.AttachedToVisualTree event is invoked
  2. In the handler for the above event, Attach is called for the child of the layer
    Image
  3. The adorner is added to the Children of the AdornerLayer
  4. ChildrenCollectionChanged event is fired for AdornerLayer.Children
  5. UpdateAdornedElement is called for the adorner
  6. adorner.CompositionVisual is null, so adorner.CompositionVisual.AdornedVisual is not set. (nor AdornerIsClipped)
    Image
  7. 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.

UpdateAdornedElement(i, i.GetValue(AdornedElementProperty)); < removed

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:
Image

Finally,

This might be related. I don't really know, but I see "adorner" and "compositor" in the title, so...maybe? 😅

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions