在 .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 内的主控件中添加边距。因此 hostWindow
的 Padding
相应更改。
有一种方法无需创建自己的 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中的例子。
在 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 内的主控件中添加边距。因此 hostWindow
的 Padding
相应更改。
有一种方法无需创建自己的 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中的例子。