WPF窗体样式重写实战:ControlTemplate与WindowChrome深度解析
简介这份资源面向具备一定 WPF 基础的开发者聚焦窗体样式重写与界面美化这一实战主题帮助解决默认窗体外观单调、难以打造个性化交互体验的问题。压缩包共 72 个文件约 221KB以 cs 源码、xaml 与 baml 界面定义文件为主辅以 dll、exe、png 图片及 csproj、sln 工程文件构成一个可直接编译运行的完整解决方案。资源围绕自定义 Style、重写 ControlTemplate、数据绑定与 Trigger 响应式设计等核心知识点展开并配有 MyMacSolution 项目实践涵盖主窗体、消息窗体、启动窗体等多个界面模块便于读者对照源码理解样式与模板的组织方式。目前已有 1570 人学习下载适合希望通过真实项目掌握 WPF 界面定制技巧、提升应用视觉表现力的开发者参考借鉴。1. 重写 WPF 窗体样式为什么原生控件总有一股“默认味”做过 WPF 桌面项目的人大概都有过这种体验功能都跑通了界面一截图发出去对方第一句话是“这看着像十年前的软件”。问题不在你不会写 XAML而在于你直接用了Button、TextBox、ComboBox这些控件的默认模板——它们由系统主题决定圆角、渐变、边框、鼠标悬停色全是微软替你定好的你改几个属性根本压不住。所谓“WPF 窗体样式重写”本质是把控件的ControlTemplate和窗体的WindowChrome从默认值换成自己画的一套让界面从“系统给的”变成“你设计的”。这件事适合两类人一类是接外包或做内部工具需要界面拿得出手另一类是已经会写业务逻辑但一碰样式就靠第三方 UI 库想搞清楚底层到底怎么运作。下面这套路径是我自己在多个桌面项目里反复用过的不依赖任何商业控件库纯 WPF 原生能力就能落地。2. 先搞懂模板与样式的作用边界别把 Style 当 Template 用2.1 Style、ControlTemplate、DataTemplate 三者的分工很多人翻车的起点是把Style和ControlTemplate混为一谈。Style是一组属性值的集合负责改颜色、字体、间距这类“外观属性”ControlTemplate决定控件长什么样、由哪些视觉元素拼成是“结构”。DataTemplate则是数据怎么显示跟控件本身无关。举个具体例子你想让按钮圆角改Style里的Background是没用的因为默认模板里画背景的是一个Border你得进ControlTemplate去改那个Border的CornerRadius。这个边界搞不清就会出现“我明明设了属性却没反应”的玄学现象。选型上我一般遵循一个原则能用Style解决的绝不动ControlTemplate。因为重写模板意味着你要自己处理所有视觉状态——正常、悬停、按下、禁用、聚焦少一个状态用户就会觉得“这按钮点着没反馈”。只有默认模板的结构确实满足不了设计稿时才整体重写。2.2 用 Style 做最小改动一个按钮的配色与状态先看不动模板能做到什么程度。下面这段给按钮换了配色并处理了悬停和按下两个状态Style x:KeyFlatButtonStyle TargetTypeButton !-- 基础外观只改属性不碰模板 -- Setter PropertyBackground Value#2D7FF9/ Setter PropertyForeground ValueWhite/ Setter PropertyBorderThickness Value0/ Setter PropertyPadding Value16,8/ Setter PropertyFontSize Value14/ Setter PropertyCursor ValueHand/ !-- 用触发器处理交互状态避免写模板 -- Style.Triggers Trigger PropertyIsMouseOver ValueTrue Setter PropertyBackground Value#1B6FE0/ /Trigger Trigger PropertyIsPressed ValueTrue Setter PropertyBackground Value#1558B8/ /Trigger Trigger PropertyIsEnabled ValueFalse Setter PropertyBackground Value#B0BEC5/ Setter PropertyCursor ValueArrow/ /Trigger /Style.Triggers /Style逻辑说明TargetType锁定按钮类型Setter批量设属性Style.Triggers里的Trigger监听依赖属性变化。参数上Padding控制文字与边框的距离Cursor设成Hand是桌面端按钮的惯例禁用态一定要单独处理否则灰得不明显。这套写法能覆盖大部分“换个皮肤”的需求代码量小、可维护。但它有个硬边界默认模板的圆角、边框阴影改不了因为那些由模板内部的Border和Chrome决定。2.3 什么时候必须重写 ControlTemplate判断标准很直接设计稿里出现了默认模板结构里没有的元素或者默认结构的多余元素去不掉。比如设计稿要求按钮左侧有个图标、右侧有个角标默认模板只有一个ContentPresenter塞不下这种布局又比如要求按钮是胶囊形且带投影默认的Border圆角是写死的。这时候就得整体重写。重写不是从零画而是把默认模板的结构抄出来删掉不要的加上要的。获取默认模板有个常用技巧在 XAML 里给控件设Template前先用设计器或运行时把ControlTemplate导出成 XAML再改。我一般直接手写因为默认模板里塞了大量主题资源引用抄出来反而更乱。3. 重写 ControlTemplate从按钮到输入框的完整模板3.1 按钮模板视觉状态用 VisualStateManager 管理重写模板的核心是把控件的各个状态用VisualStateManager管起来而不是散落的Trigger。下面是一个带圆角和悬停反馈的按钮模板ControlTemplate x:KeyRoundButtonTemplate TargetTypeButton Border x:Namebd Background{TemplateBinding Background} CornerRadius6 SnapsToDevicePixelsTrue ContentPresenter HorizontalAlignmentCenter VerticalAlignmentCenter Margin{TemplateBinding Padding}/ /Border ControlTemplate.Triggers Trigger PropertyIsMouseOver ValueTrue Setter TargetNamebd PropertyOpacity Value0.88/ /Trigger Trigger PropertyIsPressed ValueTrue Setter TargetNamebd PropertyOpacity Value0.72/ /Trigger Trigger PropertyIsEnabled ValueFalse Setter TargetNamebd PropertyBackground Value#CFD8DC/ /Trigger /ControlTemplate.Triggers /ControlTemplate逻辑说明TemplateBinding把控件自身的Background、Padding透传进模板这样外部Style设的值仍然生效模板和样式解耦。TargetName指向模板内命名的元素触发器才能精确改到它。参数上CornerRadius是圆角半径SnapsToDevicePixels开起来能减少边缘模糊这个在非整数缩放屏幕上尤其明显。注意ContentPresenter的Margin绑到Padding否则你设的Padding在模板里会失效——这是新手最常踩的坑之一。3.2 输入框模板去掉系统边框自己画聚焦态TextBox的默认模板在聚焦时会有一圈系统蓝边框很多设计稿不想要。重写时把PART_ContentHost保留其余自己画ControlTemplate x:KeyFlatTextBoxTemplate TargetTypeTextBox Border x:Nameborder Background{TemplateBinding Background} BorderBrush#B0BEC5 BorderThickness1 CornerRadius4 ScrollViewer x:NamePART_ContentHost Margin{TemplateBinding Padding} VerticalAlignmentCenter/ /Border ControlTemplate.Triggers Trigger PropertyIsKeyboardFocused ValueTrue Setter TargetNameborder PropertyBorderBrush Value#2D7FF9/ Setter TargetNameborder PropertyBorderThickness Value2/ /Trigger /ControlTemplate.Triggers /ControlTemplate逻辑说明PART_ContentHost是TextBox模板里必须存在的命名元素WPF 靠它挂载文本编辑逻辑名字不能改类型必须是ScrollViewer或AdornerDecorator。参数上聚焦时把边框从 1 加到 2 会带来 1 像素的布局抖动如果在意可以改成只换颜色不换粗细或者用Effect加外发光。这个细节在像素级还原设计稿时会被反复提到。3.3 把模板挂到 Style 上并全局生效模板写好了要挂进Style再通过App.xaml的ResourceDictionary全局生效Application.Resources ResourceDictionary ResourceDictionary.MergedDictionaries ResourceDictionary SourceThemes/Controls.xaml/ /ResourceDictionary.MergedDictionaries !-- 隐式样式不写 x:Key自动应用到所有 Button -- Style TargetTypeButton BasedOn{StaticResource FlatButtonStyle} Setter PropertyTemplate Value{StaticResource RoundButtonTemplate}/ /Style /ResourceDictionary /Application.Resources逻辑说明MergedDictionaries把样式文件拆出去方便多项目复用。隐式样式不写x:Key会自动应用到作用域内所有同类型控件省去逐个指定。参数上BasedOn让新样式继承已有样式避免重复写属性。注意隐式样式的作用域是资源字典所在的范围放在App.xaml就是全局放在某个UserControl.Resources就只影响那个控件内部。4. 窗体本身也要重写WindowChrome 与无边框窗口4.1 用 WindowChrome 保留系统行为很多人做无边框窗口时直接设WindowStyleNone和AllowsTransparencyTrue结果窗口拖不动、最大化会盖住任务栏、阴影也没了。正确做法是用WindowChrome它让你自定义标题栏的同时保留系统的拖拽、缩放、贴边行为Window x:ClassDemo.MainWindow xmlns:shellclr-namespace:System.Windows.Shell;assemblyPresentationFramework WindowStyleNone ResizeModeCanResize AllowsTransparencyFalse shell:WindowChrome.WindowChrome shell:WindowChrome CaptionHeight36 ResizeBorderThickness6 GlassFrameThickness0 CornerRadius0/ /shell:WindowChrome.WindowChrome Grid !-- 自定义标题栏CaptionHeight 区域内自动可拖拽 -- Border Height36 Background#1F2937 VerticalAlignmentTop TextBlock Text模拟项目X ForegroundWhite VerticalAlignmentCenter Margin12,0/ /Border /Grid /Window逻辑说明CaptionHeight定义顶部多高算标题栏这个区域内系统自动处理拖拽和双击最大化。ResizeBorderThickness是边缘拖拽缩放的感应宽度。GlassFrameThickness设 0 去掉系统玻璃效果。参数上CaptionHeight要和你的自定义标题栏高度一致否则会出现“拖不动”或“拖拽区域错位”。AllowsTransparency保持False能避免性能下降和某些渲染问题除非你确实需要不规则窗口。4.2 自定义标题栏里的按钮要设 IsHitTestVisibleInChrome标题栏里放最小化、最大化、关闭按钮时这些按钮会被WindowChrome当成拖拽区域点不动。解决办法是给按钮加附加属性Button Content✕ shell:WindowChrome.IsHitTestVisibleInChromeTrue Width46 Height36 Style{StaticResource CaptionButtonStyle} ClickCloseButton_Click/逻辑说明IsHitTestVisibleInChrome告诉WindowChrome这个元素要接收鼠标事件不要当拖拽区。参数上标题栏按钮的宽度一般设 46 左右和系统窗口按钮尺寸接近用户习惯。关闭按钮的悬停色通常用红色这是跨平台的通用约定。4.3 最大化时的边距处理无边框窗口最大化后内容会顶到屏幕边缘甚至盖住任务栏。需要在Window的StateChanged事件里动态调整Border的Paddingprivate void Window_StateChanged(object sender, EventArgs e) { // 最大化时留出系统边框厚度避免内容被裁切 if (WindowState WindowState.Maximized) { RootBorder.Padding new Thickness(8); } else { RootBorder.Padding new Thickness(0); } }逻辑说明最大化时系统会保留一圈不可见的边框内容如果不留边距就会被裁掉一部分。参数上Thickness(8)是经验值不同 DPI 下可能需要调整更稳妥的做法是读取SystemParameters.WindowResizeBorderThickness再换算。这个坑在 125% 缩放的屏幕上特别容易暴露。5. 避坑与排查样式重写里最容易翻车的五件事5.1 现象改了模板控件完全不显示原因通常是模板里缺少必要的命名元素。比如TextBox模板必须有PART_ContentHostComboBox必须有PART_EditableTextBox和PART_PopupProgressBar必须有PART_Track和PART_Indicator。这些名字是控件内部逻辑硬编码查找的缺了就直接不渲染或抛异常。解决方式是查该控件的默认模板确认所有PART_开头的元素都在。我一般会在重写前先把默认模板导出看一眼心里有数再动手。5.2 现象样式在某个窗口里不生效原因是资源查找是沿着视觉树向上找的如果某个窗口或控件自己定义了同类型的隐式样式会覆盖全局的。排查方法是看该窗口的Resources里有没有同TargetType的Style。解决方式是统一把样式收口到App.xaml局部要覆盖时用x:Key显式引用避免隐式冲突。另一个常见原因是MergedDictionaries的加载顺序后加载的会覆盖先加载的顺序错了样式就被顶掉。5.3 现象悬停时按钮闪烁或状态卡住原因是Trigger的优先级和Setter冲突。比如你在Style里设了Background又在ControlTemplate.Triggers里设同一个属性模板触发器的优先级更高但如果你在代码里动态改了Background就会和触发器打架。解决方式是交互状态一律走模板触发器或VisualStateManager不要在代码里直接改视觉属性。如果确实要动态改先清除触发器或改用绑定加转换器。5.4 现象无边框窗口拖动时卡顿原因是AllowsTransparencyTrue加上大量半透明元素WPF 会走软件渲染路径。解决方式是尽量不用AllowsTransparency改用WindowChrome如果必须用减少窗口内的半透明层数和Effect数量。另一个原因是窗口内容过于复杂拖动时每帧重绘可以在拖动期间临时降低渲染质量但更根本的还是简化视觉树。5.5 现象高 DPI 下界面模糊或错位原因是 WPF 默认按设备无关单位布局但图片、图标、边框在非整数缩放下会插值。解决方式是给Window加UseLayoutRoundingTrue和SnapsToDevicePixelsTrue图标优先用矢量Path而不是位图。参数上UseLayoutRounding让布局对齐到物理像素能显著改善文字和边框的清晰度。这个设置建议全局开代价是某些动画可能不够平滑但桌面工具类应用清晰度优先。6. 进阶把样式做成可切换主题的资源字典6.1 主题切换的目录结构把颜色、字体、控件模板分层放切换主题时只换颜色字典Themes/ Colors.Light.xaml // 浅色主题的颜色定义 Colors.Dark.xaml // 深色主题的颜色定义 Controls.xaml // 控件模板引用颜色键 Typography.xaml // 字体与字号Controls.xaml里所有颜色都用{DynamicResource Color.Primary}这种键引用而不是写死十六进制。这样切换主题时只需要替换Colors.*.xaml控件模板完全不用动。6.2 运行时切换主题的代码public void SwitchTheme(string themeName) { // 找到颜色字典并替换 var dict new ResourceDictionary { Source new Uri($Themes/Colors.{themeName}.xaml, UriKind.Relative) }; var old Application.Current.Resources.MergedDictionaries .FirstOrDefault(d d.Source ! null d.Source.OriginalString.Contains(Colors.)); if (old ! null) { int index Application.Current.Resources.MergedDictionaries.IndexOf(old); Application.Current.Resources.MergedDictionaries[index] dict; } }逻辑说明DynamicResource在资源变化时会自动更新引用它的元素所以替换字典后界面即时刷新。参数上UriKind.Relative表示相对路径打包成单文件发布时要注意资源路径的处理。用DynamicResource而不是StaticResource是主题切换的前提后者只在加载时解析一次换了字典也不会更新。6.3 一个验证样式是否彻底解耦的技巧把Controls.xaml单独拿出来在一个空白窗口里只加载它看控件能不能正常显示。如果能说明模板不依赖任何外部资源如果报找不到资源说明有硬编码的颜色或字体引用。这个“隔离测试”是我每次做完主题都会跑一遍的能提前发现大部分资源引用问题。另一个习惯是给每个颜色键写注释说明用途比如Color.Primary用于主按钮Color.Surface用于卡片背景团队协作时省去大量沟通。样式重写这件事说到底是把“系统替你决定”的部分拿回来自己管。一开始会觉得麻烦但一旦模板和资源分层理顺了后面加控件、换主题、适配设计稿都是顺水推舟。我自己的教训是早期图快直接在窗口里写死颜色结果项目做到一半要换深色模式改了几百处那才叫后悔药没处买。所以宁可前期多花半天把资源字典搭好也别给后面埋雷。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

被动太阳能遮阳系统建模与优化实践

被动太阳能遮阳系统建模与优化实践

1. 被动太阳能遮阳策略建模与优化——以Sungrove大学与Borealis大学为例建筑节能领域有个经典悖论:为什么在空调普及的今天,我们反而需要更关注那些不用电的遮阳设计?去年夏天参与Sungrove大学图书馆改造项目时,35℃高温天里西晒区…

2026/10/14 19:28:23 阅读更多 →
MATLAB火箭弹道仿真:Falcon 9发射与着陆六自由度代码解析

MATLAB火箭弹道仿真:Falcon 9发射与着陆六自由度代码解析

简介:这份资源是围绕猎鹰9号火箭发射与着陆过程的MATLAB仿真项目,面向具备一定MATLAB基础、对航天动力学与控制理论感兴趣的工程学习者。项目以数值积分求解牛顿第二定律为核心,建立包含重力、空气阻力和推力的火箭动力学模型,并进…

2026/10/14 19:28:24 阅读更多 →
物料抓取机械手的结构优化设计:从力学分析到样机实测

物料抓取机械手的结构优化设计:从力学分析到样机实测

把物料从一个工位捡起来,放到另一个工位,听起来是自动化里最简单的一件事。可真要自己做一台物料抓取与转运机械手,你会发现真正折磨人的往往不是控制程序,而是结构。手臂伸出去会弯,末端抓不准、动起来会抖&#xff0…

2026/10/13 15:45:45 阅读更多 →

最新新闻

Linux调度器入门:从CFS到负载均衡,理解CPU时间分配与性能排查

Linux调度器入门:从CFS到负载均衡,理解CPU时间分配与性能排查

一年前我接手一个在线服务时遇到过一件怪事:机器负载不高,CPU 总占用也只有 30% 上下,可接口的 P99 延迟动不动就飙到几百毫秒。查了半天网络、数据库、锁,最后才发现问题出在 Linux 调度器身上——某个 CPU 密集型的后台任务把核…

2026/10/14 21:55:04 阅读更多 →
Java LinkedList源码深度解析:双向链表设计哲学与性能真相

Java LinkedList源码深度解析:双向链表设计哲学与性能真相

1. 项目概述:为什么一个链表值得花三天时间逐行读透?“JDK源码解析——LinkedList”这个标题看起来平平无奇,甚至有点老派。但如果你真打开java.util.LinkedList的源码文件,从第1行package java.util;开始往下读,到第3…

2026/10/14 21:55:04 阅读更多 →
Flink Checkpoint机制深度解析:原理、调优与故障排查

Flink Checkpoint机制深度解析:原理、调优与故障排查

1. 项目概述:为什么“checkpoint”这个词在Flink现场总被反复提起“Flink checkpoint杂谈”——光看标题,你可能以为这是篇技术随笔,但实际它背后站着的是整个流处理系统稳定运行的命门。我在某公司做实时数仓架构支撑的三年里,几…

2026/10/14 21:55:04 阅读更多 →
温湿度阈值模型生产落地:调度框架与三层架构实践复盘

温湿度阈值模型生产落地:调度框架与三层架构实践复盘

搞数据分析或者物联网数据接入的朋友,应该都有过这种体验:模型在离线脚本里跑得飞飞起,准确率看着也不错,可一旦要接入业务系统、定时执行、还得让运维同学能接手维护,就开始到处冒烟。最近我正好把一个温湿度阈值模型…

2026/10/14 21:55:04 阅读更多 →
微博评论情感分析实战:从爬虫清洗到BERT+CNN微调

微博评论情感分析实战:从爬虫清洗到BERT+CNN微调

简介:本资源是一套完整的微博中文评论情感分析实战项目,面向Python初学者及NLP入门学习者,聚焦社交媒体文本挖掘与情感倾向判别这一典型应用场景。项目基于20万条真实微博评论数据,提供从数据清洗、jieba分词、停用词过滤&#xf…

2026/10/14 21:55:04 阅读更多 →
URP中HDR贴图全链路实战:从导入到Shader采样与性能优化

URP中HDR贴图全链路实战:从导入到Shader采样与性能优化

1. 为什么普通贴图做不出真实光照做Unity渲染的人,迟早会在某个下午盯着一个半成品场景发愁:明明贴图分辨率够高、模型没毛病、光照方向也对,但渲染结果就是“塑料感”十足——高光发灰、暗部死黑、Bloom效果怎么调都像一层廉价雾。这时候如果…

2026/10/14 21:54:01 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →