在 .net Avalonia 中更改 Window 的系统顶部栏的背景颜色的更简单方法?

Easier way in .net Avalonia to change the background color of the Window's System Top bar?

在 dotnet 的 Avalonia-UI 框架中。 我使用的是深色 UI 并且我设法按照 this example 将所有内容设为黑色,但有一点:window 的系统顶部栏位于 Windows OS.

我在 this issue in github 中看到我可以设置 属性 HasSystemDecorations="false" 让它消失,但是我必须自己实现带有拖动功能的顶部栏, title, close, maximize, minimize 等,当我只想改变背景颜色时,这很痛苦。

将 window 顶部栏更改为深色背景颜色的更简单方法是什么?

如果唯一的方法是使用 HasSystemDecorations 那么实现具有与 close/minimize/maximize/drag 相同功能的深色顶部栏的最小示例是什么?

是的,您必须设置 HasSystemDecorations="false" 并实现您自己的标题栏。我在 Github 上有一个基本模板,用于说明如何使用 0.10 版和流畅的主题来执行此操作。

其实很简单,因为Avalonia提供了很多方便的方法来实现。

概述:

设置

ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"

为Window再实现一个标题栏。例如,关闭按钮可能看起来像这样:

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

如果你在一个控件上设置IsHitTestVisible="False",下面的window可以拖到那个区域。因此,将整个标题栏包装在例如 DockPanel 中:

<DockPanel Background="Black"
           IsHitTestVisible="False"
           Name="TitleBarBackground"></DockPanel>

现在您显然仍然需要模仿按钮的行为。原则上可以这样做(再次查看具体示例,请查看上面的 Github 存储库):

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;
}

现在最后一步是您需要根据 window 状态更改最大化按钮的图标。比如你拖动一个最大化的window,它会自动恢复向下,并且最大化按钮的图标需要改变。因此,您需要订阅主机 window 的 window 状态,可以这样做:

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";
            }
        });
    }

实际上在上面的代码片段中还有一个细节需要注意。至少在 windows 上,最大化的 window 实际上比屏幕还大。如果您不希望您的内容超出屏幕边界,您需要在 window 内的主控件中添加边距。因此 hostWindowPadding 相应更改。

有一种方法无需创建自己的 minimize/maximize/close 按钮(我只在 Windows 上测试过)。

在你的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>

这里是AvaloniaUI documentation中的例子。

这里是an example in a real project with a black system bar