尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

WPF自定义标题栏:WindowChrome与DWM API实现深色主题适配

WPF自定义标题栏:WindowChrome与DWM API实现深色主题适配 1. 项目概述为什么需要自定义WPF标题栏如果你用WPF做过桌面应用大概率会对那个默认的、灰扑扑的窗口标题栏感到审美疲劳。尤其是在设计现代化UI时一个与整体主题格格不入的标题栏瞬间就能把应用的“高级感”拉低好几个档次。我们想把它改成和主界面背景一致的深色或者任何符合设计语言的色彩这几乎是每个WPF开发者都会遇到的刚需。但事情远没有设置一个Background属性那么简单。当你尝试修改Window的Background或WindowStyle时会发现标题栏上的最小化、最大化、关闭这三个系统按钮它们的颜色并不会自动适应。在浅色背景下它们可能是黑色的在深色背景下它们又可能变成白色导致根本看不清。这就是标题里提到的“需要调一下”的核心痛点如何让系统按钮的颜色与自定义的背景色和谐共存确保在任何主题下都清晰可用这个需求背后是WPF框架设计的一个经典权衡为了性能和兼容性标准窗口WindowStyle”SingleBorderWindow”或”None”的标题栏是由操作系统Windows原生绘制的WPF应用对其控制力有限。因此我们需要一些“非标准”的手段来介入这个绘制过程。网上有很多零散的方案有的用Win32 API有的用第三方库但往往只解决了背景色忽略了按钮状态或者代码冗长难以维护。今天我们就来系统地拆解这个问题提供一个从原理到实践、兼顾美观与功能的完整解决方案。2. 核心思路拆解从系统绘制到自定义接管要彻底解决标题栏自定义问题我们必须先理解WPF窗口的几种形态及其控制力边界。2.1 WPF窗口样式与标题栏控制力分析WPF的Window类有一个WindowStyle属性它决定了窗口的边框和标题栏样式。主要有以下几种WindowStyle”SingleBorderWindow”(默认值)这是最常见的窗口样式包含标准的非客户区标题栏、边框和客户区我们的WPF内容。在这种模式下标题栏完全由Windows系统绘制WPF几乎无法直接修改其背景色和按钮样式。我们只能通过一些系统级别的API进行有限干预这也是本项目的核心挑战。WindowStyle”None”彻底移除系统提供的标题栏和边框。此时整个窗口的绘制权完全交给WPF。你可以用任何XAML元素比如一个Grid加几个Button来模拟一个标题栏实现100%的自定义。但随之而来的问题是你需要自己处理窗口拖动、双击最大化、系统菜单右键标题栏等所有原生功能工作量巨大且容易与操作系统交互产生不一致的体验。WindowStyle”ThreeDBorderWindow”/”ToolWindow”这两种样式现在较少使用控制力同样受限。我们的目标很明确在保留原生窗口良好交互体验系统按钮功能、窗口拖动、Aero Snap等的前提下尽可能深度的自定义标题栏外观。这意味着我们主要与WindowStyle”SingleBorderWindow”打交道。2.2 技术方案选型为何选择WindowChrome面对系统绘制的标题栏WPF社区探索出了几条主要路径P/Invoke调用Win32 API直接调用SetWindowCompositionAttribute、DwmSetWindowAttribute等函数可以设置标题栏的模糊效果、颜色等。这是最底层、最强大的方式但代码复杂需要处理大量的平台调用和结构体且在不同Windows版本如Win7, Win10, Win11上行为可能不一致维护成本高。使用第三方库如MahApps.Metro, ModernWpf这些优秀的UI库封装了上述底层逻辑提供了开箱即用的Window控件。如果你的项目本身就在使用这些库那这是最省事的选择。但如果你只想解决标题栏问题引入一个完整的UI框架可能显得过于沉重。使用WPF原生WindowChrome类这是WPF 4.5之后引入的一个“中间层”方案。它允许你将WPF内容扩展到非客户区即标题栏区域同时通过GlassFrameThickness等属性告诉系统哪些区域如右上角的按钮区仍然由系统处理。这是我们本次选择的方案。为什么选择WindowChrome因为它提供了一个绝佳的平衡点。你无需处理复杂的Win32调用就能将自定义的XAML内容一个Grid里面可以放Logo、标题、你的自定义按钮铺满整个窗口顶部覆盖掉原生的标题栏。同时通过正确配置WindowChrome的属性系统按钮的区域会被“保留”出来它们仍然是原生的、功能完整的按钮只是视觉上被你的背景覆盖了。我们的任务就变成了1. 用自定义背景覆盖标题栏。2. 确保系统按钮在自定义背景下可见。3. 实现自定义标题栏背景色理论清晰了我们开始动手。首先创建一个标准的WPF项目.NET Framework 4.6.1 或 .NET Core 3.1/ .NET 5。3.1 基础布局与WindowChrome应用我们先在MainWindow.xaml中搭建基础结构。核心是为Window设置WindowChrome。Window x:ClassCustomTitleBarDemo.MainWindow xmlnshttp://schemas.microsoft.com/winfx/2006/xaml/presentation xmlns:xhttp://schemas.microsoft.com/winfx/2006/xaml Title自定义标题栏示例 Height450 Width800 WindowStyleSingleBorderWindow ResizeModeCanResize BackgroundTransparent !-- 关键窗口背景透明避免边缘色差 -- !-- 应用WindowChrome -- WindowChrome.WindowChrome WindowChrome CaptionHeight32 !-- 指定标题栏的高度即可拖动和放置系统按钮的区域 -- ResizeBorderThickness6 !-- 窗口边缘调整大小的边框厚度 -- GlassFrameThickness0 !-- 将玻璃框厚度设为0禁用Aero Glass效果 -- CornerRadius0/ /WindowChrome.WindowChrome Grid !-- 外层Grid作为主容器设置我们想要的窗口背景色例如深色 -- Grid Background#FF252526 Grid.RowDefinitions !-- 第一行自定义标题栏区域高度与CaptionHeight匹配 -- RowDefinition Height32/ !-- 第二行窗口主内容区域 -- RowDefinition Height*/ /Grid.RowDefinitions !-- 自定义标题栏内容 -- Grid Grid.Row0 Background#FF252526 !-- 这里可以放置你的Logo、窗口标题、自定义按钮等 -- TextBlock Text我的WPF应用 VerticalAlignmentCenter HorizontalAlignmentLeft Margin10,0 ForegroundWhite/ !-- 注意系统按钮最小化、最大化、关闭是自动悬浮在这个Grid之上的 -- /Grid !-- 窗口主内容区 -- Border Grid.Row1 Background#FF1E1E1E Margin10 TextBlock Text这里是应用的主内容区域 ForegroundWhite VerticalAlignmentCenter HorizontalAlignmentCenter/ /Border /Grid /Grid /Window关键点解析WindowStyle”SingleBorderWindow”和ResizeMode”CanResize”必须保留以确保窗口具有标准边框和可调整大小的能力。Window.Background”Transparent”非常重要。这能防止窗口客户区边缘与自定义背景之间出现不匹配的色带。WindowChrome.CaptionHeight这个值定义了系统认为的“标题栏”区域。在这个区域内用户可以通过鼠标拖动来移动窗口并且系统按钮会占据其右上角。这个高度需要与你设计的自定义标题栏视觉高度一致。GlassFrameThickness”0”禁用透明的Aero玻璃效果让我们的纯色背景能够正常显示。此时运行程序你会发现窗口顶部变成了深色#FF252526但最小化、最大化、关闭按钮变成了默认的白色或黑色在深色背景上几乎看不见。这正是我们要解决的核心问题。3.2 动态适配系统按钮颜色Light/Dark主题感知系统按钮的颜色是由Windows根据其“窗口主题”自动决定的。在浅色主题下按钮是深色在深色主题下按钮是浅色。我们的目标是让Windows知道我们的窗口标题栏是深色的从而自动使用浅色按钮。从Windows 10 版本 1607周年更新开始微软引入了DWMWA_USE_IMMERSIVE_DARK_MODE等属性允许应用程序声明其窗口模式。对于 .NET 5 / .NET Core 3.1 及更高版本的WPF微软在Microsoft.Win32命名空间下提供了Window类的扩展方法使得设置更加简单。但对于 .NET Framework我们仍需借助一些P/Invoke。下面提供一个兼容性较好的方案它尝试使用较新的API并回退到旧API。在MainWindow.xaml.cs的构造函数中调用using System; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // 在窗口源码初始化完成后尝试设置深色标题栏 this.Loaded (s, e) { TrySetDarkTitleBar(); }; } private void TrySetDarkTitleBar() { var windowHandle new WindowInteropHelper(this).Handle; if (windowHandle IntPtr.Zero) { // 如果句柄尚未创建延迟到SourceInitialized事件中执行 this.SourceInitialized (s, e) { SetDarkTitleBar(new WindowInteropHelper(this).Handle); }; } else { SetDarkTitleBar(windowHandle); } } private void SetDarkTitleBar(IntPtr hwnd) { if (hwnd IntPtr.Zero) return; // 方法1: 尝试使用 Windows 11 / 10 较新版本的 API (DWMWA_USE_IMMERSIVE_DARK_MODE) // 这个属性值需要根据系统版本谨慎使用这里作为首选尝试 if (Environment.OSVersion.Version.Major 10) { try { // DWMWA_USE_IMMERSIVE_DARK_MODE 20 const int DWMWA_USE_IMMERSIVE_DARK_MODE 20; // 1 表示启用沉浸式深色模式 int useImmersiveDarkMode IsDarkBackground() ? 1 : 0; DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref useImmersiveDarkMode, sizeof(int)); } catch { /* 忽略API不存在或调用失败的情况 */ } } // 方法2: 回退到设置标题栏颜色的旧API (DWMWA_CAPTION_COLOR) // 注意这个API在某些系统版本上可能不改变按钮颜色只改变标题栏背景。 // 但它是一个更通用的后备方案。 try { // 判断当前自定义标题栏的背景色是否为深色 if (IsDarkBackground()) { // DWMWA_CAPTION_COLOR 35 (Windows 11, 也可能在Win10后期版本支持) const int DWMWA_CAPTION_COLOR 35; // 将颜色转换为ABGR格式DWM通常使用0xAARRGGBB但这里需要BGR // 我们取标题栏背景色的近似值这里假设是 #FF252526 uint darkColor 0xFF262626; // 一个近似的深灰色 DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, ref darkColor, sizeof(uint)); } } catch { /* 忽略失败 */ } } // 一个简单的启发式方法判断当前设置的背景色是否为深色 private bool IsDarkBackground() { // 这里需要获取你实际用于标题栏的背景色。 // 为了示例我们假设你用的是在XAML中为标题栏Grid设置的Background。 // 更健壮的做法是查找视觉树中标题栏Grid的背景色。 // 此处简化为检查窗口根Grid的背景色假设你按上文XAML设置了深色。 if (this.Background is SolidColorBrush scb) { var color scb.Color; // 计算相对亮度 (ITU-R BT.709) double luminance (0.2126 * color.R 0.7152 * color.G 0.0722 * color.B) / 255; return luminance 0.5; // 亮度小于0.5认为是深色 } // 如果背景不是纯色或者没找到根据常见设计默认返回true深色模式流行 return true; } [DllImport(dwmapi.dll)] private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref int attrValue, int attrSize); [DllImport(dwmapi.dll)] private static extern int DwmSetWindowAttribute(IntPtr hwnd, int attr, ref uint attrValue, int attrSize); }重要提示DwmSetWindowAttributeAPI的行为在不同Windows版本上差异很大。DWMWA_USE_IMMERSIVE_DARK_MODE在Windows 10 1607 和 Windows 11上效果最好能同时影响标题栏背景和按钮颜色。DWMWA_CAPTION_COLOR主要影响标题栏背景对按钮颜色的影响不确定。上述代码提供了两种尝试以增加成功率。实操心得测试是关键务必在你的目标Windows版本如Win10 21H2, Win11 22H2等上进行充分测试。不同版本的系统其DWM渲染逻辑可能有细微差别。句柄时机操作必须在窗口句柄hwnd创建之后。Loaded事件通常是个安全的选择但SourceInitialized事件是WindowInteropHelper能获取到有效句柄的最早时机。颜色判断逻辑上面的IsDarkBackground()方法非常简化。在生产环境中你应该有一个更可靠的方式来获取或传递你为标题栏设定的主题色深色/浅色例如通过一个绑定的属性。4. 处理最大化窗口时的布局问题当你点击最大化按钮时可能会发现自定义标题栏的内容被屏幕顶部“吞掉”了一小部分。这是因为窗口在最大化时系统会隐藏窗口边框而我们的WindowChrome设置可能没有完全适应这种状态。4.1 检测窗口状态并调整边距我们需要监听窗口的StateChanged事件并在窗口最大化时为顶部内容添加一个边距以补偿消失的窗口边框。首先给标题栏的Grid起个名字方便在代码中访问Grid x:NameTitleBarArea Grid.Row0 Background#FF252526 !-- ... 内容 ... -- /Grid然后在代码中处理public MainWindow() { InitializeComponent(); this.Loaded (s, e) { TrySetDarkTitleBar(); }; // 监听窗口状态变化 this.StateChanged MainWindow_StateChanged; } private void MainWindow_StateChanged(object sender, EventArgs e) { if (this.WindowState WindowState.Maximized) { // 窗口最大化时系统边框消失。我们需要在顶部补偿一点边距 // 防止内容紧贴屏幕边缘。这个值通常是系统边框的厚度8像素是一个常见值。 TitleBarArea.Margin new Thickness(8, 8, 8, 0); // 同时可能需要调整WindowChrome的ResizeBorderThickness因为最大化后不能调整大小。 // 但更简单的做法是在最大化时临时将WindowChrome的ResizeBorderThickness设为0。 // 这需要获取到WindowChrome对象并修改稍微复杂。一个常见的替代方案是 // 设置Window的ResizeModeNoResize当最大化时但这会影响还原后的行为。 // 对于大多数应用仅调整标题栏边距已足够。 } else { // 恢复正常或最小化状态时重置边距 TitleBarArea.Margin new Thickness(0); } }4.2 使用Blur效果增强视觉层次可选如果你觉得纯色标题栏略显平淡可以为其添加一个亚克力或背景模糊效果使其与后面的窗口内容产生层次感。这可以通过WindowChrome结合VisualBrush或SystemDropShadowChrome已过时模拟但更现代的方式是使用Windows API CodePack或Microsoft.Toolkit.Win32.UI.XamlHost等访问更底层的模糊API。不过这涉及到更复杂的互操作。一个相对简单且效果不错的WPF原生方案是在标题栏区域叠加一个半透明的矩形并对其背景进行模糊处理虽然模糊的是其背后的父级元素效果有限。Grid x:NameTitleBarArea Grid.Row0 !-- 底层实色背景 -- Rectangle Fill#80252526/ !-- 使用带透明度的颜色例如80%不透明度 -- !-- 上层模糊效果层 (对底层背景进行模糊) -- Rectangle Rectangle.Fill VisualBrush Visual{Binding ElementNameMainContentGrid} / !-- 绑定到主内容Grid -- /Rectangle.Fill Rectangle.Effect BlurEffect Radius10/ /Rectangle.Effect Rectangle.OpacityMask LinearGradientBrush StartPoint0,0 EndPoint0,1 GradientStop Color#CCFFFFFF Offset0/ GradientStop Color#00FFFFFF Offset1/ /LinearGradientBrush /Rectangle.OpacityMask /Rectangle !-- 最上层标题栏实际内容文字、按钮等 -- TextBlock Text我的WPF应用 ... ForegroundWhite/ /Grid这个方案性能开销较大且效果取决于背后的内容。对于追求极致性能和视觉效果的项目建议研究DwmExtendFrameIntoClientArea或UWP/WinUI的AcrylicBrush在WPF中的集成方案。5. 常见问题与排查技巧实录在实际实现过程中你肯定会遇到一些“坑”。下面是我踩过之后总结出来的常见问题及解决方法。5.1 系统按钮仍然不显示或颜色不对这是最常见的问题。检查WindowChrome.CaptionHeight这个值必须大于0且应该等于你自定义标题栏视觉区域的高度。如果设置太小系统按钮区域可能被你的内容覆盖而无法点击如果设置太大标题栏下方会有一块不可拖动的空白。检查API调用是否成功在SetDarkTitleBar方法中可以检查DwmSetWindowAttribute的返回值。返回0S_OK表示成功。非0表示失败。可以在catch块外或使用Marshal.GetLastWin32Error()获取错误码进行调试。Windows版本兼容性DWMWA_USE_IMMERSIVE_DARK_MODE在旧版Windows 10上无效。如果你的应用需要支持较早版本要有降级方案比如提供一个浅色主题的备选标题栏或者使用完全自定义按钮WindowStyle”None”的方案。高DPI缩放在高DPI显示器上CaptionHeight和边距可能需要根据SystemParameters进行缩放计算否则布局可能错位。double dpiScale VisualTreeHelper.GetDpi(this).DpiScaleX; int scaledCaptionHeight (int)(32 * dpiScale); // 假设设计高度为32杀毒软件或系统优化工具干扰极少数情况下第三方软件可能会钩住或修改窗口绘制过程导致异常。可以尝试在干净启动环境下测试。5.2 窗口拖动区域不准确或失效确保可拖动区域覆盖了整个CaptionHeight区域WindowChrome只认CaptionHeight定义的矩形区域为可拖动区。如果你在这个区域的某个部分放置了Button等控件并且没有处理其鼠标事件那么鼠标在该控件上就无法拖动窗口。你需要为这些控件设置WindowChrome.IsHitTestVisibleInChrome”True”。Button Content我的按钮 WindowChrome.IsHitTestVisibleInChromeTrue/将这个属性设为True意味着该控件在WindowChrome区域内的点击测试对Chrome即窗口管理器是可见的从而允许事件穿透到系统进行窗口拖动。如果设为False默认控件会拦截鼠标事件导致无法拖动。自定义标题栏内有需要交互的控件如果标题栏内有你自己的按钮如设置、帮助你当然不希望点击它们时触发窗口拖动。这时就不要为这些控件设置IsHitTestVisibleInChrome”True”或者更精细地处理PreviewMouseLeftButtonDown等事件。5.3 窗口边框闪烁或绘制异常设置Window.Background”Transparent”如前所述这能避免窗口客户区边缘与自定义背景的冲突。避免在Window根元素上设置纯色背景将窗口背景色设置在你的主内容Grid或Border上而不是Window标签本身。在窗口显示前完成设置尽量在SourceInitialized或Loaded事件中完成所有WindowChrome和DWM属性的设置避免窗口显示后动态修改导致重绘闪烁。5.4 性能考虑慎用复杂视觉效果在标题栏区域使用BlurEffect、DropShadowEffect或复杂的VisualBrush会显著增加GPU负载尤其是在窗口频繁重绘如拖动、调整大小时。对于需要高性能的应用建议使用纯色或简单的渐变。WindowChrome的开销WindowChrome本身比原生窗口标题栏有额外的性能开销因为它需要协调WPF渲染与系统非客户区的交互。但在现代硬件上这点开销对于绝大多数应用来说可以忽略不计。6. 进阶完全自定义按钮与交互如果你对原生系统按钮的样式或行为仍有不满例如想要圆角按钮、更流畅的动画或者DwmSetWindowAttribute的方案在你的目标系统上不工作那么最后的退路就是使用WindowStyle”None”并完全自己实现标题栏和按钮。6.1 实现无边框窗口与自定义标题栏Window x:ClassCustomTitleBarDemo.CustomWindow ... WindowStyleNone ResizeModeCanResize AllowsTransparencyTrue !-- 允许透明便于实现圆角等效果 -- BackgroundTransparent !-- 使用WindowChrome来保留窗口调整大小的功能 -- WindowChrome.WindowChrome WindowChrome CaptionHeight0 !-- 因为我们要完全自定义所以将系统标题栏高度设为0 -- ResizeBorderThickness6 GlassFrameThickness0 CornerRadius0/ /WindowChrome.WindowChrome Grid !-- 窗口主体包含阴影和圆角 -- Border Background#FF252526 CornerRadius8 Margin10 !-- 外Margin模拟窗口阴影 -- Grid Grid.RowDefinitions RowDefinition Height32/ RowDefinition Height*/ /Grid.RowDefinitions !-- 完全自定义的标题栏 -- Grid Grid.Row0 BackgroundTransparent MouseLeftButtonDownTitleBar_MouseLeftButtonDown Grid.ColumnDefinitions ColumnDefinition WidthAuto/ ColumnDefinition Width*/ ColumnDefinition WidthAuto/ /Grid.ColumnDefinitions Image Sourcelogo.png Grid.Column0 Margin10,0 VerticalAlignmentCenter/ TextBlock Text完全自定义标题栏 Grid.Column1 VerticalAlignmentCenter ForegroundWhite/ !-- 自定义窗口控制按钮 -- StackPanel Grid.Column2 OrientationHorizontal Button x:NameMinimizeButton Style{StaticResource CustomWindowButtonStyle} Content─ ClickMinimizeButton_Click/ Button x:NameMaximizeRestoreButton Style{StaticResource CustomWindowButtonStyle} Content□ ClickMaximizeRestoreButton_Click/ Button x:NameCloseButton Style{StaticResource CustomWindowButtonStyle} Content× ClickCloseButton_Click BackgroundRed/ /StackPanel /Grid !-- 主内容区 -- Border Grid.Row1 Background#FF1E1E1E Margin10 !-- 你的应用内容 -- /Border /Grid /Border /Grid /Window6.2 实现窗口拖动与按钮功能在代码后台你需要实现拖动逻辑和按钮命令private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount 2) { // 双击标题栏切换最大化/还原 MaximizeRestoreWindow(); } else { // 拖动窗口 this.DragMove(); } } private void MinimizeButton_Click(object sender, RoutedEventArgs e) { this.WindowState WindowState.Minimized; } private void MaximizeRestoreButton_Click(object sender, RoutedEventArgs e) { MaximizeRestoreWindow(); } private void MaximizeRestoreWindow() { if (this.WindowState WindowState.Maximized) { this.WindowState WindowState.Normal; // 可以在这里更新按钮图标为“最大化” MaximizeRestoreButton.Content □; } else { this.WindowState WindowState.Maximized; // 更新按钮图标为“还原” MaximizeRestoreButton.Content ❐; // 或使用其他符号/图标 } } private void CloseButton_Click(object sender, RoutedEventArgs e) { this.Close(); }6.3 自定义按钮样式与状态你需要为自定义按钮设计一套样式以模拟系统按钮的悬停、按下等状态。Window.Resources Style x:KeyCustomWindowButtonStyle TargetTypeButton Setter PropertyForeground ValueWhite/ Setter PropertyBackground ValueTransparent/ Setter PropertyBorderThickness Value0/ Setter PropertyWidth Value45/ Setter PropertyHeight Value32/ Setter PropertyFontSize Value14/ Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeButton Border Background{TemplateBinding Background} x:Nameborder ContentPresenter HorizontalAlignmentCenter VerticalAlignmentCenter/ /Border ControlTemplate.Triggers Trigger PropertyIsMouseOver ValueTrue Setter TargetNameborder PropertyBackground Value#44FFFFFF/ /Trigger Trigger PropertyIsPressed ValueTrue Setter TargetNameborder PropertyBackground Value#88FFFFFF/ /Trigger /ControlTemplate.Triggers /ControlTemplate /Setter.Value /Setter /Style /Window.Resources完全自定义方案的优缺点优点视觉控制力100%可以做出任何你想要的效果不受系统版本限制。缺点工作量翻倍需要实现所有窗口管理逻辑拖动、双击最大化、系统菜单调用等。失去原生体验Aero Snap、任务栏预览等高级系统交互可能无法完美复现或需要额外大量工作。潜在兼容性问题不同Windows版本和主题下你的自定义样式可能需要不断调整。我个人在实际项目中的选择策略是优先使用WindowChrome DWM API的方案。它平衡了开发成本、维护成本和用户体验。只有当产品对标题栏UI有极其特殊的要求且目标系统明确不支持DWM深色模式API时才会考虑完全自定义的方案。对于大多数业务应用第一种方案经过妥善调整后已经能提供足够专业和美观的视觉效果。
返回列表