WinUI3学习Blog(2):自定义标题栏+窗口背景材质
本篇内容有点复杂,也有些难度,所以建议跳过这篇往后面看几篇再回来,尤其是自定义标题栏和改变背景材质
WinUI3学习Blog(2):自定义标题栏+窗口背景材质
开始之前:
本篇内容有点复杂,也有些难度,所以建议跳过这篇往后面看几篇再回来,尤其是自定义标题栏和改变背景材质
本篇文章继续沿用上一篇文章的仓库
本篇目标:
1、自定义标题栏
2、改变窗口材质(云母和亚克力)
3.TextBlock文字标签是个啥(是叫做标签?不管了)
自定义标题栏
看到很多系统应用,应该都没有系统标题栏,而是将整个页面扩展到了标题栏中
所以,接下来将会说说怎么弄
仅仅是将系统标题栏隐藏掉
注意:这种方法比较简单,但是不能添加按钮等可以用户交互的控件,因为你会发现交互不了
使用上一篇的MainWindow,仓库提交bfa108
首先,我们要在XAML中用布局的方法写一个标题栏的UI(MainWindow.xaml)
<Grid
x:Name="AppTitleBar"
Height="48"
Margin="48,0,0,0"
VerticalAlignment="Top"
Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="12,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="FirstWinUI"/>
</Grid>
上面的代码中,用了一个Grid布局,名字叫AppTitleBar
里面放了一个TextBlock,这个东西是个文字标签(是这么叫?),相当于Label,标签上的文字是“FirstWinUI”
后面那一堆属性不管他,下篇研究
上一篇中的MainWindow.xaml用了一个StackPanel来在窗口中间。但是,一个页面(不管是Window还是Page)最外层只能有一个布局
所以,为了保留这个按钮的位置,我们要在这个标题栏的布局(上面的),和这个按钮的布局(StackPanel),外面套一层Grid,就像这样
<Grid>
<Grid
x:Name="AppTitleBar"
Height="48"
Margin="48,0,0,0"
VerticalAlignment="Top"
Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock
Margin="12,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
Text="FirstWinUI"/>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="myButton" Click="myButton_Click">Click Me</Button>
</StackPanel>
</Grid>
~~这就是传说中的嵌套布局吧!~~
然后,我们要在代码中应用上这个标题栏(MainWindow.xaml.cs)
在public MainWindow()底下
ExtendsContentIntoTitleBar = true; // 将页面扩展到标题栏中(隐藏了传统的标题栏)
SetTitleBar(AppTitleBar); // 设置标题栏是名字叫“AppTitleBar”的那个布局
注释中已经说明每行代码是干啥的了
最后长这样就对了:
public MainWindow()
{
this.InitializeComponent();
ExtendsContentIntoTitleBar = true; // 将页面扩展到标题栏中(隐藏了传统的标题栏)
SetTitleBar(AppTitleBar); // 设置标题栏是名字叫“AppTitleBar”的那个布局
}
简单运行一下,可以发现是生效的

只不过,你把那个按钮也扔到标题栏上试试:(MainWindow.xaml)仓库提交:30ac367
<Grid>
<Grid
x:Name="AppTitleBar"
Height="48"
Margin="12,0,0,0"
VerticalAlignment="Top"
Padding="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDet="FirstWinUI"/>
<Button x:Name="myButton" Margin="12,0,0,0" Click="myButton_Click">finitions>
<TextBlock
Margin="12,0,0,0"
Grid.Column="1"
VerticalAlignment="Center"
Style="{StaticResource CaptionTextBlockStyle}"
TexClick Me</Button>
</Grid>
</Grid>
运行后你会发现,根本点不开,双击直接最大化了。。。

所以,为了能让这个按钮正常工作,我们必须要完全自定义
完全自定义
参考:微软官方运行不了的文档, 仓库提交:7fe3ef
这个标题栏UI的样子保持不动,我们只需要改代码文件
标题栏,分为可以拖动的区域(图标,标题文字,空白区域等),和可以交互的区域(按钮~~,不包括三大金刚~~)
拖动的区域不进行控件的交互,交互的区域不能拖。所以最后代码被造成了这样(MainWindow.xaml.cs):
public sealed partial class MainWindow : Window
{
private AppWindow m_AppWindow;
private Window m_window;
public MainWindow()
{
this.InitializeComponent();
// 初始化一堆乱七八糟的
m_AppWindow = this.AppWindow;
Activated += MainWindow_Activated;
AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
AppTitleBar.Loaded += AppTitleBar_Loaded;
ExtendsContentIntoTitleBar = true;
if (ExtendsContentIntoTitleBar == true)
{
m_AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
}
TitleBarTextBlock.Text = AppInfo.Current.DisplayInfo.DisplayName;
ExtendsContentIntoTitleBar = true; // 将页面扩展到标题栏中(隐藏了传统的标题栏)
}
private void myButton_Click(object sender, RoutedEventArgs e)
{
m_window = new AnotherWindow();
m_window.Activate();
}
private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
{
if (ExtendsContentIntoTitleBar == true)
{
// 当页面画布扩展到标题栏的时候,初始化交互的区域
SetRegionsForCustomTitleBar();
}
}
private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (ExtendsContentIntoTitleBar == true)
{
// 在页面画布扩展到标题栏的前提下,当窗口大小变化时更新交互区域
SetRegionsForCustomTitleBar();
}
}
private void SetRegionsForCustomTitleBar()
{
// 计算标题栏的区域到底有多大
// 交互区域, 就是那个按钮的区域,那个需要和用户交互
double scaleAdjustment = AppTitleBar.XamlRoot.RasterizationScale;
RightPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.RightInset / scaleAdjustment);
LeftPaddingColumn.Width = new GridLength(m_AppWindow.TitleBar.LeftInset / scaleAdjustment);
// 获取按钮矩形的区域
GeneralTransform transform = this.myButton.TransformToVisual(null);
Rect bounds = transform.TransformBounds(new Rect(0, 0,
this.myButton.ActualWidth,
this.myButton.ActualHeight));
Windows.Graphics.RectInt32 myButton = GetRect(bounds, scaleAdjustment);
// 标题栏中有几个交互控件顶上这5行就复制几次,然后把变量都改成控件的Name,最后弄出来的区域按到底下那个var里边就行,如果看不懂的话
var rectArray = new Windows.Graphics.RectInt32[] { myButton };
InputNonClientPointerSource nonClientInputSrc =
InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
}
private Windows.Graphics.RectInt32 GetRect(Rect bounds, double scale)
{
// Rect事矩形区域
return new Windows.Graphics.RectInt32(
_X: (int)Math.Round(bounds.X * scale),
_Y: (int)Math.Round(bounds.Y * scale),
_Width: (int)Math.Round(bounds.Width * scale),
_Height: (int)Math.Round(bounds.Height * scale)
);
}
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
// 当窗口不活动时弄成灰色的,活动时就不弄成灰色的
if (args.WindowActivationState == WindowActivationState.Deactivated)
{
TitleBarTextBlock.Foreground =
(SolidColorBrush)App.Current.Resources["WindowCaptionForegroundDisabled"];
}
else
{
TitleBarTextBlock.Foreground =
(SolidColorBrush)App.Current.Resources["WindowCaptionForeground"];
}
}
}
MainWindow.xaml也要加一点料,把<Grid.ColumnDefinitions>这一块弄成这样
这样主要是为了计算最左侧的区域和最右侧三大金刚的区域
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="LeftPaddingColumn" Width="0"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition x:Name="RightPaddingColumn" Width="0"/>
</Grid.ColumnDefinitions>
~~反正乱七八糟,有点C#和Windows开发基础的差不多应该看得懂,看不懂把变量换换复制粘贴也能跑~~
我在文档代码基础上把注释翻译了一下,然后又加了一些注释。~~对于复制粘贴改变量的,这些注释应该够理解得了(~~
不要忘记添加引用
using Windows.ApplicationModel;
using Rect = Windows.Foundation.Rect;
using Microsoft.UI.Windowing; //添加引用
这样就行了,运行可以看到按钮可以正常点,拖动也没有问题

紧凑视图和全屏视图
注意:这部分涉及一些按钮操作的知识,请先往后看,看完按钮部分后再回来看这个部分
众所周知,WinUI是有紧凑视图和全屏视图的,可以使用AppWindowPresenterKind来进行切换
所以,现在就要往窗口中添加三个按钮,分别为全屏,紧凑,和默认 [部分目标]
参考:微软官方运行不了的文档, 仓库提交:a8ff216
先往MainWindow.xaml上扔三个按钮,操作都指向SwitchPresenter(不知道为啥都指向一个操作的就跳过这个部分往后面看)
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="CompactoverlaytBtn"
Content="紧凑"
Click="SwitchPresenter"/>
<Button x:Name="FullscreenBtn"
Content="全屏"
Click="SwitchPresenter"/>
<Button x:Name="OverlappedBtn"
Content="默认"
Click="SwitchPresenter"/>
</StackPanel>
在初始化一堆乱七八糟那底下再加一个(MainWindow.xaml.cs)
this.InitializeComponent();
// 初始化一堆乱七八糟的
m_AppWindow = this.AppWindow;
m_AppWindow.Changed += AppWindow_Changed; // 按上初始化 // 加的是这句,上面的原来都有
然后在类里按上几个操作,用来使用AppWindowPresenterKind
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
{
if (args.DidPresenterChange)
{
switch (sender.Presenter.Kind)
{
case AppWindowPresenterKind.CompactOverlay:
// 紧凑视图中,隐藏自定义的的标题栏,而是使用系统的标题栏
// 因为这种时候,自定义的标题栏会被当成普通的控件来伺候
// 当然,你乐意也行, 效果也差不了多少
AppTitleBar.Visibility = Visibility.Collapsed; // 隐藏自定义标题栏
sender.TitleBar.ResetToDefault(); // 使用系统默认标题栏
break;
case AppWindowPresenterKind.FullScreen:
// 全屏的时候也隐藏自定义的标题栏,因为自定义的标题栏也会被当成普通的控件来伺候
AppTitleBar.Visibility = Visibility.Collapsed; // 隐藏自定义标题栏
sender.TitleBar.ExtendsContentIntoTitleBar = true; // 画布依旧扩展到标题栏上
break;
case AppWindowPresenterKind.Overlapped:
// 重叠的时候(就是非常普通,启动后的样子),使用的是我们自己自定义的标题栏
AppTitleBar.Visibility = Visibility.Visible;
sender.TitleBar.ExtendsContentIntoTitleBar = true;
break;
default:
// 使用系统默认标题栏
sender.TitleBar.ResetToDefault();
break;
}
}
}
private void SwitchPresenter(object sender, RoutedEventArgs e)
{
if (AppWindow != null)
{
AppWindowPresenterKind newPresenterKind;
switch ((sender as Button).Name)
{
case "CompactoverlaytBtn": // 紧凑
newPresenterKind = AppWindowPresenterKind.CompactOverlay;
break;
case "FullscreenBtn": // 全屏
newPresenterKind = AppWindowPresenterKind.FullScreen;
break;
case "OverlappedBtn": // 重叠(就是你不动它的时候)
newPresenterKind = AppWindowPresenterKind.Overlapped;
break;
default: // 啥也不是(使用系统默认,这个就让系统决定)
newPresenterKind = AppWindowPresenterKind.Default;
break;
}
// 如果在这个模式中又按了这个模式的按钮,就滚回默认的模式(系统决定的)
if (newPresenterKind == AppWindow.Presenter.Kind)
{
AppWindow.SetPresenter(AppWindowPresenterKind.Default);
}
else
{
// 如果不是,就切换
AppWindow.SetPresenter(newPresenterKind);
}
}
}
注释里有对代码的解释,看不懂可以直接Copy。运行起来还可以

窗口背景材质
Windows11中引用了云母(Mica)材质,对桌面进行了模糊处理,效果还可以,这里就拿这个当例子说
在WindowsAppSDK1.3中简化了使用这些材质的过程,使其几行代码就能搞定
方法一:在XAML中设置
仓库提交:b02a18a
打开MainWindow.xaml,把下面这点东西丢到窗口的布局上面,Window标签那一堆IntelliSense(就是我从来不管的东西)底下
<Window.SystemBackdrop>
<MicaBackdrop Kind="Base"/>
</Window.SystemBackdrop>
然后直接跑就行
当然你也可以用亚克力,把<MicaBackdrop Kind="Base"/>换成<DesktopAcrylicBackdrop/>就行
MicaBackdrop中的Kind也可以换成BaseAlt,效果也有些不同
方法二:在代码中设置
部分目标:在窗口中再按上仨按钮,分别可以切换三种背景材料。仓库提交:372f032
首先先按上三个按钮,按下后触发SetBackdrop函数。(MainWindow.xaml)
<StackPanel Orientation="Horizontal">
<Button x:Name="MicaBaseBtn"
Content="MicaBase"
Click="SetBackdrop"/>
<Button x:Name="MicaBaseAltBtn"
Content="MicaBaseAlt"
Click="SetBackdrop"/>
<Button x:Name="AcrylicBtn"
Content="Acrylic"
Click="SetBackdrop"/>
</StackPanel>
为了放到原先三个按钮的底下,这里再次嵌套一个StackPanel,最后连着上面的三个按钮,代码长这样
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Button x:Name="CompactoverlaytBtn"
Content="紧凑"
Click="SwitchPresenter"/>
<Button x:Name="FullscreenBtn"
Content="全屏"
Click="SwitchPresenter"/>
<Button x:Name="OverlappedBtn"
Content="默认"
Click="SwitchPresenter"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<Button x:Name="MicaBaseBtn"
Content="MicaBase"
Click="SetBackdrop"/>
<Button x:Name="MicaBaseAltBtn"
Content="MicaBaseAlt"
Click="SetBackdrop"/>
<Button x:Name="AcrylicBtn"
Content="Acrylic"
Click="SetBackdrop"/>
</StackPanel>
</StackPanel>
然后写逻辑代码,丢到MainWindow类里随便一个位置(MainWindow.xaml.cs)
private void SetBackdrop(object sender, RoutedEventArgs e)
{
if (AppWindow != null)
{
switch ((sender as Button).Name)
{
case "MicaBaseBtn": // 按钮MicaBase
SystemBackdrop = new MicaBackdrop()
{ Kind = MicaKind.Base }; //进行更换
break;
case "MicaBaseAltBtn": // 按钮MicaBaseAlt
SystemBackdrop = new MicaBackdrop()
{ Kind = MicaKind.BaseAlt }; //进行更换
break;
case "AcrylicBtn": // 按钮Acrylic
SystemBackdrop = new DesktopAcrylicBackdrop(); //进行更换
break;
}
}
}
和上面的那仨按钮的逻辑一样,这里也是使用了switch-case。注释有解释,不再多说。
运行的时候窗口也是Mica是因为在Xaml中设置过了。如果没有设置过就没有效果。这个时候可以在窗口初始化那一堆底下加这个东西:
SystemBackdrop = new MicaBackdrop()
{ Kind = MicaKind.Base }
这样就可以直接运行了,效果如下

三种材质区别
Acrylic:

MicaBaseAlt:

MicaBase:

总结
这篇讲了自定义标题栏和背景材质两个内容 ~~(我喜欢)~~
不会可以先跳过,因为东西有点超前。~~但理论上来说,看着注释复制粘贴改改变量应该也可以跑~~
字数太多了,下篇讲往窗口上按Page
© 2026 ntcho & ConiMite • mstouk57g
最后更新于: 2026-01-15 03:45:46