Describe the bug
Issue:
When using WindowDrawnDecorations, controls placed inside it cannot obtain the parent TopLevel via TopLevel.GetTopLevel(this).
var topLevel = TopLevel.GetTopLevel(this);
where this is a control hosted inside WindowDrawnDecorations.Overlay.
GetTopLevel() returns null because WindowDrawnDecorations does not appear to be part of the visual tree. The content is only connected through the logical tree, which makes APIs that rely on visual tree traversal unable to locate the owning window/top-level.
This is surprising because controls hosted in WindowDrawnDecorations are visually rendered as part of the window chrome, yet they behave differently from regular window content.
Is this intentional behavior? If so, what is the recommended way for controls inside WindowDrawnDecorations to access their owning TopLevel or Window?
It would be helpful if either:
TopLevel.GetTopLevel() worked as expected from controls hosted in WindowDrawnDecorations, or
- the documentation explicitly described this limitation and the recommended alternative approach.
Actual behavior:
TopLevel.GetTopLevel(this) returns null because the control is not connected to the visual tree leading to the window.
To Reproduce
Place a custom control into WindowDrawnDecorations.Overlay and try resolving the TopLevel via TopLevel.GetTopLevel(this) from code-behind.
Expected behavior
TopLevel.GetTopLevel(this) should return the owning TopLevel for controls hosted inside WindowDrawnDecorations.
Avalonia version
12.0.4
OS
Windows
Additional context
We encountered this while implementing a DialogHost. We placed it in WindowDrawnDecorations.Overlay so that the dialog overlay would also cover the title bar and window content. In this scenario, TopLevel.GetTopLevel() returns null from controls inside the decorations content, making it impossible to obtain the owning window through the usual API. Additionally, Popups (combobox, context menu, flyout...) do not appear to work in this scenario.
TextBox events for copy/cut do not work either with this setup.
Our current WindowDrawnDecorations theme:
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:dialogHost="clr-namespace:DialogHostAvalonia;assembly=DialogHost.Avalonia"
xmlns:dialogHostEx="clr-namespace:ICAFluentUINet.AvaloniaUI.Controls.ContentDialog;assembly=ICAFluentUINet.AvaloniaUI"
xmlns:ic="clr-namespace:ICAFluentUINet.AvaloniaUI.Controls.WindowChrome;assembly=ICAFluentUINet.AvaloniaUI">
<!-- Add Resources Here -->
<ControlTheme x:Key="{x:Type WindowDrawnDecorations}"
TargetType="WindowDrawnDecorations">
<Setter Property="DefaultTitleBarHeight" Value="{Binding $parent[Window].(ic:TitleBarEx.TitleBarHeight)}" />
<Setter Property="DefaultFrameThickness" Value="1" />
<Setter Property="DefaultShadowThickness" Value="8" />
<!--<Setter Property="ic:TitleBarEx.TitleBarHeight" Value="50" />-->
<Setter Property="Template">
<WindowDrawnDecorationsTemplate>
<WindowDrawnDecorationsContent>
<WindowDrawnDecorationsContent.Underlay>
<Panel x:Name="PART_UnderlayWrapper">
<Border x:Name="PART_WindowBorder"
Background="{DynamicResource SystemControlTransparentBrush}"
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumBrush}"
BorderThickness="{TemplateBinding FrameThickness}"
IsHitTestVisible="False" />
</Panel>
</WindowDrawnDecorationsContent.Underlay>
<WindowDrawnDecorationsContent.Overlay>
<!-- Overlay: only interactive caption buttons -->
<Panel x:Name="PART_OverlayWrapper">
<Grid x:Name="PART_Container"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RowDefinitions="auto,*">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<dialogHost:DialogHost x:Name="PART_DialogHost"
Grid.RowSpan="2"
Grid.Column="0"
Grid.ColumnSpan="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutomationProperties.ControlTypeOverride="Pane"
CloseOnClickAway="False"
Identifier="{Binding $parent[Window].(dialogHostEx:DialogHostEx.Identifier)}"
ZIndex="99" />
<!-- ColumnDefinitions="auto,*,auto,135" -->
<Panel x:Name="PART_TitleBar"
Grid.Column="0"
Grid.ColumnSpan="5"
Height="{TemplateBinding TitleBarHeight}"
VerticalAlignment="Top"
Background="Transparent"
Classes.TopMost="{Binding #PART_DialogHost.IsOpen}"
IsVisible="{TemplateBinding HasTitleBar}"
WindowDecorationProperties.ElementRole="TitleBar">
<Panel.Styles>
<Style Selector="Panel.TopMost">
<Setter Property="ZIndex" Value="2147483647" />
</Style>
</Panel.Styles>
</Panel>
<Panel x:Name="PART_TitleBarBackground"
Grid.Column="0"
Grid.ColumnSpan="5"
Height="{TemplateBinding TitleBarHeight}"
VerticalAlignment="Top"
Background="{Binding $parent[Window].(ic:TitleBarEx.Background)}"
IsVisible="{TemplateBinding HasTitleBar}"
ZIndex="-1" />
<Panel x:Name="PART_TitleTextPanel"
VerticalAlignment="Stretch"
IsVisible="{TemplateBinding HasTitleBar}">
<StackPanel Grid.Row="0"
Grid.Column="1"
VerticalAlignment="Top"
Orientation="Horizontal"
Spacing="12">
<Button x:Name="PART_BackButton"
MinHeight="30"
MaxHeight="30"
VerticalAlignment="Top"
AutomationProperties.Name="Back"
Command="{Binding $parent[Window].(ic:TitleBarEx.BackButtonCommand)}"
IsVisible="{Binding $parent[Window].(ic:TitleBarEx.BackButtonIsVisible)}"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="User">
<Viewbox Width="15"
Margin="2">
<Path Data="{StaticResource ArrowLeftRegularIcon_Path}"
Fill="{Binding $parent[Window].Foreground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
<TextBlock x:Name="PART_Title"
Margin="12,0,0,0"
VerticalAlignment="Center"
AutomationProperties.Name="{Binding $self.Text}"
Classes.MarginTop="{Binding !#PART_BackButton.IsVisible}"
IsHitTestVisible="False"
IsVisible="{Binding $parent[Window].(ic:TitleBarEx.TitleIsVisible)}"
Text="{Binding $parent[Window].Title}">
<TextBlock.Styles>
<Style Selector="TextBlock.MarginTop">
<Setter Property="Margin" Value="12 6 0 0" />
</Style>
<Style Selector="TextBlock:not(TextBlock.MarginTop)">
<Setter Property="Margin" Value="12 0 0 0" />
</Style>
</TextBlock.Styles>
</TextBlock>
</StackPanel>
</Panel>
<ContentPresenter x:Name="PART_TitleBarExContent"
Grid.Row="0"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Content="{Binding $parent[Window].(ic:TitleBarEx.Content)}"
WindowDecorationProperties.ElementRole="User" />
<StackPanel x:Name="PART_OverlayPanel"
Grid.Column="4"
HorizontalAlignment="Right"
VerticalAlignment="Top"
IsVisible="{TemplateBinding HasTitleBar}"
Orientation="Horizontal"
Spacing="2"
TextElement.FontSize="10"
ZIndex="2147483647">
<Button x:Name="PART_HelpButton"
Grid.Column="3"
MinHeight="30"
MaxHeight="30"
VerticalAlignment="Top"
AutomationProperties.Name="Help"
Classes.Overlayed="{Binding #PART_DialogHost.IsOpen}"
Command="{Binding $parent[Window].(ic:TitleBarEx.HelpButtonCommand)}"
FontSize="14"
IsVisible="{Binding $parent[Window].(ic:TitleBarEx.HelpButtonIsVisible)}"
Theme="{StaticResource FluentDrawnCaptionButton}">
<Button.Styles>
<Style Selector="Button.Overlayed">
<Setter Property="IsEnabled" Value="False" />
</Style>
</Button.Styles>
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="?" />
</Grid>
</Button>
<Button x:Name="PART_FullScreenButton"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="FullScreenButton">
<Viewbox Width="11"
Margin="2">
<Path Name="FullScreenButtonPath"
Data="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
<Button x:Name="PART_MinimizeButton"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="MinimizeButton">
<Viewbox Width="11"
Margin="2">
<Path Data="M2048 1229v-205h-2048v205h2048z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
<Button x:Name="PART_MaximizeButton"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="MaximizeButton">
<Viewbox Width="11"
Margin="2">
<Viewbox.RenderTransform>
<RotateTransform Angle="-90" />
</Viewbox.RenderTransform>
<Path Name="RestoreButtonPath"
Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
<Button x:Name="PART_CloseButton"
Background="#ffe81123"
BorderBrush="#fff1707a"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="CloseButton">
<Viewbox Width="11"
Margin="2">
<Path Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
</StackPanel>
</Grid>
</Panel>
</WindowDrawnDecorationsContent.Overlay>
<WindowDrawnDecorationsContent.FullscreenPopover>
<Panel Height="{TemplateBinding DefaultTitleBarHeight}"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}"
WindowDecorationProperties.ElementRole="TitleBar">
<TextBlock Margin="12,0,0,0"
VerticalAlignment="Center"
FontSize="12"
Text="{TemplateBinding Title}" />
<StackPanel HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="2"
TextElement.FontSize="10">
<Button x:Name="PART_PopoverFullScreenButton"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="FullScreenButton">
<Viewbox Width="11"
Margin="2">
<Path Data="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
<Button x:Name="PART_PopoverCloseButton"
Background="#ffe81123"
BorderBrush="#fff1707a"
Theme="{StaticResource FluentDrawnCaptionButton}"
WindowDecorationProperties.ElementRole="CloseButton">
<Viewbox Width="11"
Margin="2">
<Path Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"
Fill="{DynamicResource CaptionButtonForeground}"
Stretch="UniformToFill" />
</Viewbox>
</Button>
</StackPanel>
</Panel>
</WindowDrawnDecorationsContent.FullscreenPopover>
</WindowDrawnDecorationsContent>
</WindowDrawnDecorationsTemplate>
</Setter>
<!-- Shadow: inset border and add drop shadow -->
<Style Selector="^:has-shadow /template/ Panel#PART_UnderlayWrapper">
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}" />
</Style>
<Style Selector="^:has-shadow /template/ Border#PART_WindowBorder">
<Setter Property="BoxShadow" Value="0 2 10 2 #80000000" />
</Style>
<Style Selector="^:has-shadow /template/ Panel#PART_OverlayWrapper">
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}" />
</Style>
<!-- Border: inset titlebar and buttons inside frame -->
<Style Selector="^:has-border /template/ Panel#PART_TitleTextPanel">
<Setter Property="Margin" Value="1,1,1,0" />
</Style>
<Style Selector="^:has-border /template/ Panel#PART_TitleBar">
<Setter Property="Margin" Value="1,1,1,0" />
</Style>
<Style Selector="^:has-border /template/ StackPanel#PART_OverlayPanel">
<Setter Property="Margin" Value="0,1,1,0" />
</Style>
<!-- Maximized: restore button path changes -->
<Style Selector="^:maximized /template/ Path#RestoreButtonPath">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<!-- Disabled buttons -->
<Style Selector="^ /template/ Button:disabled">
<Setter Property="Opacity" Value="0.2" />
</Style>
<!-- Hide caption buttons when the platform does not support the action -->
<Style Selector="^:not(:has-minimize) /template/ Button#PART_MinimizeButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:not(:has-maximize) /template/ Button#PART_MaximizeButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:not(:has-fullscreen) /template/ Button#PART_FullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:not(:has-fullscreen) /template/ Button#PART_PopoverFullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<!-- Fullscreen: hide overlay and titlebar (popover takes over) -->
<Style Selector="^:fullscreen /template/ Panel#PART_TitleTextPanel">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:fullscreen /template/ StackPanel#PART_OverlayPanel">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="^:fullscreen /template/ Panel#PART_TitleBar">
<Setter Property="IsVisible" Value="False" />
</Style>
</ControlTheme>
</ResourceDictionary>
Describe the bug
Issue:
When using WindowDrawnDecorations, controls placed inside it cannot obtain the parent TopLevel via
TopLevel.GetTopLevel(this).where
thisis a control hosted insideWindowDrawnDecorations.Overlay.GetTopLevel()returns null becauseWindowDrawnDecorationsdoes not appear to be part of the visual tree. The content is only connected through the logical tree, which makes APIs that rely on visual tree traversal unable to locate the owning window/top-level.This is surprising because controls hosted in
WindowDrawnDecorationsare visually rendered as part of the window chrome, yet they behave differently from regular window content.Is this intentional behavior? If so, what is the recommended way for controls inside
WindowDrawnDecorationsto access their owning TopLevel or Window?It would be helpful if either:
TopLevel.GetTopLevel()worked as expected from controls hosted in WindowDrawnDecorations, orActual behavior:
TopLevel.GetTopLevel(this)returnsnullbecause the control is not connected to the visual tree leading to the window.To Reproduce
Place a custom control into WindowDrawnDecorations.Overlay and try resolving the
TopLevelviaTopLevel.GetTopLevel(this)from code-behind.Expected behavior
TopLevel.GetTopLevel(this)should return the owning TopLevel for controls hosted inside WindowDrawnDecorations.Avalonia version
12.0.4
OS
Windows
Additional context
We encountered this while implementing a DialogHost. We placed it in
WindowDrawnDecorations.Overlayso that the dialog overlay would also cover the title bar and window content. In this scenario,TopLevel.GetTopLevel()returns null from controls inside the decorations content, making it impossible to obtain the owning window through the usual API. Additionally, Popups (combobox, context menu, flyout...) do not appear to work in this scenario.TextBox events for copy/cut do not work either with this setup.
Our current WindowDrawnDecorations theme: