
1. 为什么 QDockWidget 值得单独拿出来聊做桌面端工具软件尤其是那种需要同时看多个面板、频繁切换视图的工程类软件停靠窗口几乎是绕不开的交互形态。你打开任何一个稍微专业一点的 IDE、波形分析工具、三维建模软件都会看到主窗口四周挂着一堆可以拖动、可以浮动、可以关闭再拉回来的面板。Qt Widgets 里负责这套机制的核心控件就是QDockWidget。我最早接触 QDockWidget 是在做一个数据采集上位机的时候。主界面中间是一块实时曲线画布左边要放设备树右边要放属性编辑器下边要放日志输出。一开始我用的是 QSplitter 硬切结果用户抱怨说“日志面板我不想一直占着地方能不能收起来”还有人说“属性面板我想拖到右边单独放大看”。QSplitter 做不到这些改起来还特别别扭。换成 QDockWidget 之后这些问题基本是配置层面就解决了。这篇文章面向的是已经会写基本 Qt Widgets 程序、但对停靠窗口这套体系还不够熟的开发者。我会把 QDockWidget 的核心机制、布局规则、状态保存、样式定制、以及实际项目里踩过的坑按我自己的理解顺序讲一遍。你不需要提前了解太多跟着走就行。核心关键词就几个停靠窗口、浮动面板、布局区域、状态持久化、嵌套停靠。这几个词贯穿全文理解了它们QDockWidget 基本就通了。需要先说明一点QDockWidget 本身只是一个“容器外壳”它负责的是停靠行为、标题栏、浮动/关闭按钮这些框架层面的东西真正显示内容的还是你塞进去的 QWidget。很多人第一次用会困惑“为什么我的控件拖不动”原因往往就是把逻辑搞反了——你要先有 QDockWidget再把内容控件 setWidget 进去而不是反过来。2. QDockWidget 的整体设计与核心思路拆解2.1 它到底解决了什么布局问题普通布局管理器QHBoxLayout、QVBoxLayout、QGridLayout的特点是“静态占位”。控件一旦放进去位置和比例就基本定死了用户没法在运行时调整。而 QDockWidget 引入的是一套动态可重排的布局模型每个停靠窗口都是一个独立单元用户可以拖动它的标题栏把它停靠到主窗口的上下左右任意一个边缘区域也可以让它脱离主窗口变成浮动窗口甚至拖到另一个停靠窗口上面形成标签页合并。这套模型背后其实是 QMainWindow 在支撑。QDockWidget 必须挂在 QMainWindow 上才能工作因为只有 QMainWindow 内部维护了停靠区域dock area的概念。你如果把它塞进一个普通 QWidget停靠功能是失效的。这一点是设计上的硬约束不是 bug。从架构角度看QDockWidget 把“内容”和“容器行为”做了分离。内容控件只管自己怎么画停靠窗口只管自己怎么被摆放。这种分离带来的好处是同一个内容控件你可以今天放在左边停靠明天让它浮动代码几乎不用改。这在做可配置界面的时候非常关键。2.2 停靠区域与允许区域的取舍逻辑QMainWindow 有四个停靠区域Qt::LeftDockWidgetArea、Qt::RightDockWidgetArea、Qt::TopDockWidgetArea、Qt::BottomDockWidgetArea。默认情况下一个 QDockWidget 可以被拖到任意一个区域。但实际项目里我们往往要限制它的活动范围。比如日志面板它天生适合放在底部你让它停到左边会显得很怪宽度不够文字换行严重。这时候就要用setAllowedAreas()来限制。我一般的做法是设备树、工程结构树只允许左右属性编辑器只允许左右日志、控制台输出只允许上下工具栏类面板四个方向都放开这个限制不是可有可无的它直接影响用户的使用直觉。你放开全部区域用户拖来拖去最后拖出一个自己都找不到的布局反而增加困惑。限制区域本质上是在用约束引导用户这是交互设计里很常见的手法。2.3 嵌套停靠被低估的布局能力很多人不知道 QDockWidget 可以嵌套。所谓嵌套就是在一个停靠窗口内部再放一个 QMainWindow然后在这个内层 QMainWindow 上再挂 QDockWidget。这样就能实现“面板里面还有面板”的复杂布局。我做过一个信号分析工具右侧需要同时显示“通道列表”和“通道详情”而且这两个面板要能上下分割、各自独立调整。单层 QDockWidget 做不到这种内部再分割的效果用嵌套就解决了外层一个 QDockWidget 放在右侧里面塞一个 QMainWindow内层再挂两个 QDockWidget一个在上一个在下。嵌套的代价是状态保存会变复杂因为你要分别保存外层和内层的状态。这个后面讲持久化的时候会细说。但如果你确实需要多层可调整布局嵌套是目前最干净的方案比手动拼 QSplitter 再自己写拖拽逻辑要省事得多。2.4 浮动窗口与主窗口的关系QDockWidget 浮动之后它本质上变成了一个独立的顶层窗口但它和主窗口之间还保持着逻辑关联。比如主窗口关闭时浮动的停靠窗口也会跟着关闭主窗口最小化时浮动窗口的行为取决于平台。这个关联是通过 Qt 内部的父子关系和窗口标志管理的。有一个细节值得注意浮动窗口的标题栏是系统原生的还是 Qt 自绘的取决于平台和样式。在 Windows 上通常是原生标题栏在部分 Linux 桌面环境下可能是 Qt 自绘。这会导致浮动窗口的外观在不同平台上不一致。如果你对视觉一致性要求很高可以考虑禁用浮动功能setFloatable(false)或者接受这种平台差异。我的建议是除非有明确需求否则不要轻易禁用浮动因为浮动是用户整理工作区的重要手段。3. 核心细节解析与实操要点3.1 创建一个停靠窗口的最小完整代码先给一个能跑起来的最小例子把关键调用都标出来。假设主窗口类叫 MainWindow继承自 QMainWindow。#include QMainWindow #include QDockWidget #include QTextEdit #include QListWidget MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // 中间的主内容区 QTextEdit *central new QTextEdit(this); central-setPlaceholderText(主编辑区); setCentralWidget(central); // 左侧停靠窗口设备列表 QDockWidget *dockLeft new QDockWidget(设备列表, this); dockLeft-setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); QListWidget *deviceList new QListWidget(dockLeft); deviceList-addItems({设备A, 设备B, 设备C}); dockLeft-setWidget(deviceList); addDockWidget(Qt::LeftDockWidgetArea, dockLeft); // 底部停靠窗口日志 QDockWidget *dockBottom new QDockWidget(日志输出, this); dockBottom-setAllowedAreas(Qt::BottomDockWidgetArea | Qt::TopDockWidgetArea); QTextEdit *logView new QTextEdit(dockBottom); logView-setReadOnly(true); dockBottom-setWidget(logView); addDockWidget(Qt::BottomDockWidgetArea, dockBottom); }这段代码里几个点必须理解清楚。第一setWidget()只能调用一次重复调用会警告并且不生效。第二addDockWidget()的第二个参数决定初始停靠位置但最终位置还受setAllowedAreas()约束。第三内容控件的父对象设成 dock 本身这样 Qt 会自动管理生命周期dock 销毁时内容也跟着销毁不用手动 delete。3.2 标题栏、按钮与特性开关QDockWidget 的标题栏上默认有浮动按钮和关闭按钮。这两个按钮可以通过特性标志控制dockLeft-setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);三个标志分别对应可关闭、可移动、可浮动。如果你希望某个面板固定不动就把 Movable 去掉希望它不能被关掉就去掉 Closable。我一般会保留 Movable 和 Floatable只对少数“必须常驻”的面板去掉 Closable。这里有个容易踩的坑去掉 Closable 之后用户就没法通过界面关闭它了但你自己代码里还是可以调用close()或者hide()。所以如果你做了“视图菜单”来统一控制面板显隐去掉 Closable 是合理的因为显隐入口统一到了菜单里标题栏上再放个关闭按钮反而重复。标题文字用setWindowTitle()设置。注意这个标题同时会用在浮动窗口的标题栏上所以起名要兼顾两种场景别起太长。3.3 停靠窗口的初始尺寸控制这是新手最容易困惑的地方为什么我resize()了停靠窗口它还是那么窄原因是停靠窗口的尺寸最终由 QMainWindow 的布局决定你单独 resize 它往往不生效。正确的做法有两种。第一种是用resizeDocks()这是 QMainWindow 提供的接口resizeDocks({dockLeft, dockBottom}, {280, 200}, Qt::Horizontal);注意这个调用要在窗口显示之后或者至少布局完成之后才有效放在构造函数里可能不生效通常放到showEvent或者用QTimer::singleShot(0, ...)延迟执行。第二种是给内容控件设置最小尺寸和尺寸策略deviceList-setMinimumWidth(200); deviceList-setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);我一般两种结合用。resizeDocks()定初始比例内容控件的最小尺寸兜底防止用户拖得太小导致内容显示不全。这里的最小尺寸不要设太大否则在小屏幕上会挤压主内容区适得其反。3.4 标签页合并与拆分当两个停靠窗口被拖到同一个区域时Qt 默认会把它们叠成标签页。这个行为由setTabbedDockWidgets()相关机制控制。你可以用tabifyDockWidget()手动把两个 dock 合并成标签页tabifyDockWidget(dockLeft, dockAnother); dockLeft-raise(); // 让 dockLeft 显示在前面标签页模式在面板很多的时候特别有用。比如右侧有“属性”“样式”“历史”三个面板全竖着排会占满整个高度合并成标签页就清爽多了。用户点标签切换比来回拖动高效。反过来如果你不希望自动合并可以在拖动时按住某个修饰键不同平台不一样或者干脆通过setAllowedAreas限制它们不在同一区域。但我的经验是标签页合并大多数时候是好事不要刻意禁用。3.5 样式定制去掉标题栏、改颜色QDockWidget 的标题栏样式在不同平台上差异很大有时候产品要求统一视觉就需要自己定制。最彻底的做法是去掉原生标题栏自己画一个QDockWidget *dock new QDockWidget(this); dock-setTitleBarWidget(new QWidget(dock)); // 空标题栏等于隐藏但这样做之后拖动功能就没了因为拖动是靠标题栏触发的。你要自己实现鼠标事件来拖动或者干脆放弃拖动。所以除非有很强的定制需求我不建议去掉标题栏。如果只是想改颜色和字体用样式表更稳妥QDockWidget { titlebar-close-icon: url(:/icons/close.svg); titlebar-normal-icon: url(:/icons/float.svg); font-size: 13px; } QDockWidget::title { background: #2d2d2d; color: #e0e0e0; padding: 6px; text-align: left; }注意QDockWidget::title这个子控件选择器它控制的就是标题栏区域。样式表能改的东西有限比如标题栏高度不太好精确控制浮动窗口的边框也可能不受样式表影响。这是 Qt 样式表在原生窗口装饰上的固有局限心里有数就行。4. 实操过程与核心环节实现4.1 从零搭一个多面板工作区假设我们要做一个“数据监控工作台”需求是这样的中间是实时曲线左侧是设备树右侧是属性面板和告警面板标签页合并底部是日志。四个面板都要能显隐、能保存布局。第一步搭主窗口骨架设置中央控件。中央控件用 QCustomPlot 或者 QChart 都行这里不展开重点是停靠部分。第二步创建四个 QDockWidget分别配置允许区域和特性。设备树只允许左右属性面板只允许左右告警面板只允许左右日志只允许上下。第三步把属性和告警合并成标签页addDockWidget(Qt::RightDockWidgetArea, dockProp); addDockWidget(Qt::RightDockWidgetArea, dockAlarm); tabifyDockWidget(dockProp, dockAlarm); dockProp-raise();第四步创建“视图”菜单把每个 dock 的toggleViewAction()加进去QMenu *viewMenu menuBar()-addMenu(视图); viewMenu-addAction(dockDevice-toggleViewAction()); viewMenu-addAction(dockProp-toggleViewAction()); viewMenu-addAction(dockAlarm-toggleViewAction()); viewMenu-addAction(dockLog-toggleViewAction());toggleViewAction()是 QDockWidget 自带的返回一个带勾选状态的 QAction勾上就显示取消就隐藏完全不用自己写逻辑。这是 Qt 提供的一个非常贴心的接口很多人不知道白白自己造轮子。第五步加一个“重置布局”的菜单项把布局恢复到初始状态。这个后面讲状态保存时一起说。4.2 布局状态的保存与恢复这是 QDockWidget 实战里最重要的功能之一。用户花时间把面板拖成自己喜欢的样子下次打开软件又变回默认体验会很差。Qt 提供了两个接口saveState()和restoreState()。// 保存 QSettings settings(MyCompany, DataMonitor); settings.setValue(mainWindow/geometry, saveGeometry()); settings.setValue(mainWindow/windowState, saveState()); // 恢复 QByteArray geo settings.value(mainWindow/geometry).toByteArray(); QByteArray state settings.value(mainWindow/windowState).toByteArray(); if (!geo.isEmpty()) restoreGeometry(geo); if (!state.isEmpty()) restoreState(state);saveState()保存的是所有停靠窗口的位置、大小、浮动状态、标签页顺序等信息返回一个 QByteArray。restoreState()把它还原回去。注意saveGeometry()和saveState()是两个不同的东西前者管主窗口自己的位置和大小后者管停靠窗口的布局两个都要存。这里有个关键细节restoreState()必须在所有 QDockWidget 都创建并 addDockWidget 之后才能调用。如果你在构造函数里先 restoreState 再创建 dock恢复会失败因为 Qt 找不到对应的 dock 对象。正确的顺序是创建所有 dock → 调用 restoreState → 如果没有保存的状态就用默认布局。还有一个版本兼容问题。saveState()保存的数据带有版本号如果 Qt 版本升级导致格式变化旧数据可能无法恢复。所以恢复失败时要能优雅降级到默认布局if (!restoreState(state)) { // 恢复失败应用默认布局 applyDefaultLayout(); }4.3 嵌套停靠的完整实现前面提到嵌套这里给一个具体实现。需求右侧一个“分析面板”内部上下分割成“频谱”和“统计”两个子面板。// 外层 dock QDockWidget *dockAnalysis new QDockWidget(分析面板, this); dockAnalysis-setAllowedAreas(Qt::RightDockWidgetArea); // 内层 QMainWindow QMainWindow *inner new QMainWindow(dockAnalysis); inner-setCentralWidget(nullptr); // 内层不需要中央控件 // 内层两个 dock QDockWidget *dockSpectrum new QDockWidget(频谱, inner); QDockWidget *dockStat new QDockWidget(统计, inner); dockSpectrum-setWidget(new QTextEdit(频谱内容, inner)); dockStat-setWidget(new QTextEdit(统计内容, inner)); inner-addDockWidget(Qt::TopDockWidgetArea, dockSpectrum); inner-addDockWidget(Qt::BottomDockWidgetArea, dockStat); dockAnalysis-setWidget(inner); addDockWidget(Qt::RightDockWidgetArea, dockAnalysis);这样内层的两个 dock 可以上下拖动调整外层整体可以停靠或浮动。保存状态时外层用主窗口的 saveState内层要单独保存settings.setValue(innerState, inner-saveState());恢复时先恢复外层再恢复内层。嵌套层数不建议超过两层再多的话状态管理会变得很麻烦用户也容易迷失。4.4 动态添加与移除停靠窗口有些场景下停靠窗口不是固定的而是根据数据动态生成。比如每打开一个文档就生成一个对应的面板。这时候要注意动态创建的 dock 必须在关闭时正确销毁否则会内存泄漏。QDockWidget *dock new QDockWidget(title, this); dock-setAttribute(Qt::WA_DeleteOnClose); // 关闭时自动删除 addDockWidget(Qt::RightDockWidgetArea, dock);设置WA_DeleteOnClose之后用户点关闭按钮dock 会被 delete。但如果你是通过toggleViewAction()隐藏它不会触发删除因为隐藏不等于关闭。这个区别要分清楚close()会触发删除hide()不会。动态 dock 的另一个问题是状态保存。如果 dock 是运行时生成的下次启动时还没生成就 restoreStateQt 会忽略这些不存在的 dock。所以动态 dock 的状态保存要单独处理通常是在生成 dock 时给它一个唯一的 objectName然后手动记录它的存在与否。4.5 objectName 的重要性saveState()和restoreState()内部是靠 objectName 来匹配 dock 的。如果你不设置 objectNameQt 会自动生成一个但自动生成的名字在每次运行时可能不同导致恢复失败。所以每个 QDockWidget 都必须显式设置 objectNamedockDevice-setObjectName(dockDevice); dockProp-setObjectName(dockProp);这个 objectName 一旦确定就不要改因为用户的配置文件里存的就是这个名字。改了名字等于用户之前的布局全部失效。我在项目里会把所有 dock 的 objectName 集中定义成常量避免手写字符串出错。5. 常见问题与排查技巧实录5.1 停靠窗口拖不动或拖出去回不来拖不动最常见的原因是setFeatures()里没开 Movable或者setAllowedAreas()限制得太死导致当前区域不在允许范围内。检查这两个地方基本能解决。拖出去回不来通常是因为浮动窗口被拖到了屏幕外或者多显示器环境下显示器被拔掉了。这种情况用户自己很难处理所以最好提供一个“重置布局”的入口。实现很简单void MainWindow::resetLayout() { // 先把所有 dock 恢复成非浮动 for (QDockWidget *dock : findChildrenQDockWidget*()) { dock-setFloating(false); dock-show(); } // 重新应用默认布局 applyDefaultLayout(); }applyDefaultLayout()里重新调用 addDockWidget 和 resizeDocks把布局摆回初始状态。这个功能一定要有它是用户遇到布局混乱时的救命稻草。5.2 恢复布局后尺寸不对这是高频问题。原因通常是 restoreState 之后又调用了 resizeDocks或者内容控件的最小尺寸在恢复时还没生效。我的处理办法是restoreState 之后不要立即 resizeDocks如果确实需要调整用QTimer::singleShot(0, ...)延迟到事件循环下一轮再执行。另一个原因是内容控件在恢复时还没填充数据尺寸策略计算出错。比如一个 QTableWidget 还没插入行它的 sizeHint 很小恢复出来的宽度就不对。解决办法是给内容控件设一个合理的最小尺寸或者在数据加载完成后再恢复布局。5.3 标签页切换时内容不刷新标签页合并后非当前标签的内容控件可能不会收到 paintEvent导致切回来时显示的是旧内容。这个问题在自定义绘制控件上尤其明显。解决办法是在 dock 的visibilityChanged信号里触发内容刷新connect(dockProp, QDockWidget::visibilityChanged, this, [](bool visible){ if (visible) { propWidget-update(); } });注意visibilityChanged在标签页切换时也会触发因为非当前标签在视觉上是不可见的。这个信号比showEvent更可靠。5.4 浮动窗口的关闭行为异常浮动窗口关闭时默认行为是隐藏而不是销毁。如果你希望关闭即销毁要设WA_DeleteOnClose。但设了之后toggleViewAction()的勾选状态会失效因为对象已经没了。所以这两个东西要配合使用要么用隐藏模式配合 toggleViewAction要么用销毁模式自己管理菜单状态。我一般选前者因为隐藏模式更简单对象一直存在状态管理不容易出错。5.5 常见问题速查表问题现象可能原因排查方向停靠窗口拖不动Features 未开 Movable检查 setFeatures拖到某区域停不下AllowedAreas 限制检查 setAllowedAreas恢复布局失败objectName 未设置或改变检查 objectName恢复后尺寸不对内容控件最小尺寸未生效设 minimumSize 或延迟 resize标签切换内容不刷新未响应 visibilityChanged连接信号触发 update浮动窗口关不掉未设 Closable检查 setFeatures动态 dock 内存泄漏未设 WA_DeleteOnClose检查属性设置嵌套 dock 状态丢失内层状态未单独保存单独 saveState 内层5.6 几个我踩过的坑第一个坑在构造函数里调用restoreState()之前就调用了show()。这会导致恢复的布局被 show 时的默认布局覆盖。正确顺序是 restoreState 之后再 show或者干脆在 showEvent 里做恢复。第二个坑给 dock 设置了样式表里的border结果浮动窗口的边框变得很奇怪。原因是样式表同时作用于停靠状态和浮动状态而这两种状态的窗口装饰机制不同。解决办法是用选择器区分或者干脆不在 dock 级别设边框改在内容控件上设。第三个坑多个 dock 的 objectName 重复。Qt 不会报错但恢复时只有一个能生效另一个位置错乱。这个 bug 很隐蔽因为编译运行都不报错只有恢复布局时才暴露。所以 objectName 一定要保证全局唯一最好用常量管理。第四个坑在closeEvent里保存状态但用户是通过任务管理器强杀的状态没保存。这个没法完全避免但可以加一个定时自动保存比如每 30 秒存一次布局降低丢失概率。6. 进阶技巧与性能考量6.1 大量停靠窗口时的性能如果一个界面有十几个停靠窗口全部显示时布局计算会有开销。实测下来超过 20 个 dock 同时可见窗口 resize 时会有轻微卡顿。优化方向有两个一是用标签页合并减少同时可见的数量二是对不常用的 dock 延迟创建用到时才 new。延迟创建的做法是菜单里先放一个占位的 QAction用户点击时才创建真正的 dock 并 addDockWidget。这样启动时只创建常用的几个启动速度明显提升。代价是状态保存要处理“未创建的 dock”恢复时跳过它们即可。6.2 自定义标题栏按钮原生标题栏只有浮动和关闭两个按钮有时候我们想加一个“设置”按钮或者“刷新”按钮。做法是自定义一个 QWidget 作为标题栏QWidget *titleBar new QWidget(dock); QHBoxLayout *layout new QHBoxLayout(titleBar); layout-addWidget(new QLabel(设备列表)); layout-addStretch(); QToolButton *btnRefresh new QToolButton(titleBar); btnRefresh-setIcon(QIcon(:/icons/refresh.svg)); layout-addWidget(btnRefresh); dock-setTitleBarWidget(titleBar);但前面说过自定义标题栏会失去拖动功能。解决办法是在自定义标题栏上实现 mousePressEvent 和 mouseMoveEvent手动调用dock-setFloating(true)和移动窗口。这部分代码不难但要注意处理双击标题栏浮动/停靠的默认行为需要自己模拟。6.3 停靠窗口与全屏模式的配合全屏模式下停靠窗口默认还是显示的。如果你希望全屏时隐藏所有面板只留中央内容可以在进入全屏前记录各 dock 的可见状态退出全屏后恢复void MainWindow::toggleFullScreen() { if (isFullScreen()) { showNormal(); for (auto pair : dockVisibility) { pair.first-setVisible(pair.second); } } else { dockVisibility.clear(); for (QDockWidget *dock : findChildrenQDockWidget*()) { dockVisibility.append({dock, dock-isVisible()}); dock-hide(); } showFullScreen(); } }这个功能在演示或者专注模式下很有用实现也不复杂建议加上。6.4 多显示器下的浮动窗口管理多显示器环境下用户可能把某个面板拖到副屏上浮动。如果副屏被拔掉这个浮动窗口就跑到屏幕外了。Qt 本身不处理这种情况需要自己监听屏幕变化connect(qApp, QGuiApplication::screenRemoved, this, [](QScreen *screen){ for (QDockWidget *dock : findChildrenQDockWidget*()) { if (dock-isFloating() !screen-geometry().intersects(dock-geometry())) { dock-setFloating(false); // 拉回主窗口 } } });这个逻辑不完美但至少能防止窗口彻底丢失。更完善的做法是记录浮动窗口的位置屏幕恢复时再放回去但实现复杂度高看项目需求决定要不要做。7. 我个人的一些使用体会QDockWidget 这套东西刚上手会觉得接口不多、挺简单但真正用起来细节全在布局恢复和边界情况上。我做了几个带复杂面板的桌面工具之后最大的体会是objectName 和默认布局函数这两样东西一定要在项目一开始就规范好。objectName 用常量统一管理默认布局写成一个独立函数恢复失败时能随时回退。这两件事做好了后面加面板、改布局都是几分钟的事做不好每加一个面板都要重新调一遍恢复逻辑非常痛苦。另一个体会是不要过度定制标题栏。原生标题栏虽然丑一点但拖动、双击、右键菜单这些行为都是现成的用户也熟悉。自己画标题栏这些行为全要自己实现而且不同平台表现还不一样投入产出比很低。除非产品对视觉有硬性要求否则我建议保留原生标题栏最多用样式表改改颜色和图标。最后分享一个小技巧如果你想让某个面板默认是浮动的可以在 addDockWidget 之后调用setFloating(true)但要注意这必须在窗口 show 之后调用才生效否则会被布局覆盖。我一般放在showEvent里配合一个标志位确保只执行一次。这个需求不常见但遇到的时候不知道这个细节会卡很久。