'Easier way in .net Avalonia to change the background color of the Window's System Top bar?
In dotnet's Avalonia-UI framework. I'm using a dark UI and I managed to make everything dark as per this example but one thing: the window's System top bar in Windows OS.
I have seen in this issue in github that I can set the property HasSystemDecorations="false"
to make it go away, but then I would have to implement myself the top bar with the drag functionality, title, close, maximize, minimize, etc, which is a pain when all I want is to change the background color.
What would be the easier way to make the window top bar change to a dark background color?
If the only way is using HasSystemDecorations
then what would be the minimal example to implement the dark top bar with the common funcionality to close/minimize/maximize/drag?
Solution 1:[1]
Yes, you have to set HasSystemDecorations="false"
and implement your own title bar. I have a basic template on Github for how to do this using version 0.10 and fluent theme.
It is actually quite easy, because Avalonia provides a lot of convenience methods for achieving that.
Overview:
Set
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
for the Window and then implement a titlebar. For example the close button could look something like this:
<Button Width="46"
VerticalAlignment="Stretch"
BorderThickness="0"
Name="CloseButton"
ToolTip.Tip="Close">
<Button.Resources>
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
</Button.Resources>
<Button.Styles>
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Red"/>
</Style>
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button:pointerover > Path">
<Setter Property="Fill" Value="White"/>
</Style>
<Style Selector="Button:not(:pointerover) > Path">
<Setter Property="Fill" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
</Button.Styles>
<Path Margin="10,0,10,0"
Stretch="Uniform"
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"></Path>
</Button>
If you set IsHitTestVisible="False"
on a control, the window below can be dragged in that area. So wrap your whole titlebar in for example a DockPanel:
<DockPanel Background="Black"
IsHitTestVisible="False"
Name="TitleBarBackground"></DockPanel>
Now you obviously still need to mimic the behaviour of the buttons. This can be done in principal like that (again for a concrete example check out the Github repo above):
minimizeButton = this.FindControl<Button>("MinimizeButton");
maximizeButton = this.FindControl<Button>("MaximizeButton");
maximizeIcon = this.FindControl<Path>("MaximizeIcon");
maximizeToolTip = this.FindControl<ToolTip>("MaximizeToolTip");
closeButton = this.FindControl<Button>("CloseButton");
windowIcon = this.FindControl<Image>("WindowIcon");
minimizeButton.Click += MinimizeWindow;
maximizeButton.Click += MaximizeWindow;
closeButton.Click += CloseWindow;
windowIcon.DoubleTapped += CloseWindow;
private void CloseWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.Close();
}
private void MaximizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
if (hostWindow.WindowState == WindowState.Normal)
{
hostWindow.WindowState = WindowState.Maximized;
}
else
{
hostWindow.WindowState = WindowState.Normal;
}
}
private void MinimizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.WindowState = WindowState.Minimized;
}
Now the last step is that you need to change the icon of the maximize button depending on the window state. For example if you drag a maximized window, it will automatically become restored down and the icon of the maximize button needs to change. Therefore you need to subscribe to the window state of your host window, which can be done something like that:
private async void SubscribeToWindowState()
{
Window hostWindow = (Window)this.VisualRoot;
while (hostWindow == null)
{
hostWindow = (Window)this.VisualRoot;
await Task.Delay(50);
}
hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
{
if (s != WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z");
hostWindow.Padding = new Thickness(0,0,0,0);
maximizeToolTip.Content = "Maximize";
}
if (s == WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 1638h-410v410h-1638v-1638h410v-410h1638v1638zm-614-1024h-1229v1229h1229v-1229zm409-409h-1229v205h1024v1024h205v-1229z");
hostWindow.Padding = new Thickness(7,7,7,7);
maximizeToolTip.Content = "Restore Down";
}
});
}
Actually in the snippet above there is one more detail, which needs some attention. At least on windows, a maximized window is actually bigger than the screen. If you dont want your content to go out of the screens' bounds, you need to add a margin to your main control inside the window. Therefore the Padding
of the hostWindow
is changed accordingly.
Solution 2:[2]
There is a way without having to create your own minimize/maximize/close buttons (I only tested it on Windows).
In your MainWindow.axaml
:
<Window xmlns="https://github.com/avaloniaui"
...
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"/>
<Grid RowDefinitions="30,*">
<!-- Title bar -->
<Grid ColumnDefinitions="Auto,*" IsHitTestVisible="False" Background="Black">
<Image Grid.Column="0" VerticalAlignment="Center" Source="/Assets/YOUR-PATH-TO-YOUR-APP-ICON-IMAGE" Width="18" Margin="12,0,12,0" ></Image>
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="12" >YOUR-APPLICATION-TITLE-HERE</TextBlock>
</Grid>
<!-- Window content -->
<Your-User-Content-Here Grid.Row="1" Background="#222222" />
</Grid>
Here is the example in the AvaloniaUI documentation.
Here is an example in a real project with a black system bar.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Lazaro |