ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Qt QWidget 属性避坑:几何、外观、状态与批量设置

Qt QWidget 属性避坑:几何、外观、状态与批量设置 一个控件摆在你面前位置不对、颜色不对、点了没反应、缩放时糊成一团——这些问题的答案九成都能在 QWidget 的属性表里找到。我做 Qt 客户端开发这些年接手过不少半途转来的项目代码翻到最后发现真正卡住人的从来不是信号槽、也不是多线程而是最基础的 QWidget 属性没吃透geometry和frameGeometry搞混了sizePolicy随手一填palette设了却不生效windowFlags改完窗口直接消失。这篇东西就是把我自己踩过的、以及在代码评审里反复纠正别人的那些点按属性的类别重新捋一遍从几何、外观、状态交互到批量设置和排查手法尽量把“为什么这么设计”和“实际该怎么写”讲透。不管你是刚开始用 Qt Designer 拖控件的新手还是已经能写自定义控件的老手应该都能从中挑出几条以前没注意的细节。1. 先把 QWidget 的属性分成三类再动手1.1 属性多是真多但能落到三个桶里QWidget 的官方文档里属性Properties一栏列了六七十个从acceptDrops到windowTitle初次看容易懵。但如果按“改动之后系统会做什么反应”来分它们其实只落在三个桶里。第一个桶是几何桶geometry、size、pos、minimumSize、maximumSize、sizePolicy、contentsMargins、baseSize、sizeIncrement。这类属性一动布局系统QLayout就要重新协商一遍可能触发resizeEvent、moveEvent甚至连锁影响父控件的尺寸。它们的代价最高也最容易出“设了没用”的情况。第二个桶是外观桶palette、font、styleSheet虽然严格说它是 QObject 的属性、windowOpacity、cursor、toolTip、autoFillBackground。这类属性改动的直接后果是重绘或者样式重新解析通常不涉及布局但如果和 QSS 撞车就是你调半天没反应的重灾区。第三个桶是状态与行为桶enabled、visible、hidden、focusPolicy、mouseTracking、windowFlags、windowModality、contextMenuPolicy。这类属性更多是在影响事件分发路径改错了不会报错只会让控件“安静地不工作”。这么分有什么用用处在于决定你在什么时候设置它。几何属性尽量在构造阶段、控件还没 show 的时候设完外观属性注意和 QSS 的优先级状态属性则要小心某些 setter 的副作用——比如setWindowFlags会把窗口隐藏掉。1.2 属性的读取顺序构造函数、ui-setupUi、QSS 谁说了算这是新手最容易迷糊的地方我在 Qt Designer 的属性面板里把背景色设成灰色代码里又设了一遍结果运行时既不是灰的也不是我设的是主题色。为什么实际生效顺序大约是这样一条链控件构造函数里的默认值QWidget 的初始化。ui-setupUi(this)里从.ui文件恢复的属性——Designer 面板里设的东西在这一步落地。setupUi之后你自己写的代码——后写的覆盖先写的。样式表QSS——如果控件或它的祖先设了styleSheetQSS 里能表达的那部分属性背景、边框、颜色、字体大小、padding 等会覆盖前面三步的结果。关键点在第四步。QSS 的优先级的实现方式是当控件被 polish 时QStyleSheetStyle 会把 QSS 里能映射的属性写进控件的 palette 和 font 里。所以你代码里setPalette()之后如果 QSS 里也写了background-color那你的 palette 就被盖掉了。想验证这一点可以把 QSS 临时清空再跑一次如果颜色对了那问题一定在 QSS 覆盖上。我在团队里推的一条规矩是同一个视觉效果要么全用 QSS要么全用代码不要混着来。混用带来的调试成本远大于它省下的那点灵活性。2. 几何类属性位置和大小到底谁说了算2.1 geometry、frameGeometry、rect、contentsRect 的差别这四个东西名字像亲兄弟含义差得远混一次就要多调半小时。方法参考坐标系是否含窗口边框是否含边距geometry()父控件的客户区左上角否是即整个控件矩形frameGeometry()屏幕坐标顶层窗口是含标题栏和边框是rect()自身左上角恒为 (0,0,w,h)否是contentsRect()自身左上角否否已扣掉contentsMargins对子控件而言geometry()和frameGeometry()基本没区别因为子控件没有窗口装饰只有顶层窗口Qt::Window才有边框差异。frameGeometry()的用处主要在需要屏幕坐标的时候比如做无边框窗口的拖动、做吸附对齐// 顶层窗口把窗口中心对齐到屏幕中心 const QRect frame frameGeometry(); move(QApplication::primaryScreen()-availableGeometry().center() - frame.center());这里必须用frameGeometry()而不是geometry()因为move()移动的是包含边框的外框而geometry()给的是不含边框的客户区用错会有半个标题栏的偏移看着不起眼在多屏不同 DPI 场景下会放大成十几像素。contentsRect()则常被用在自定义绘制里。比如你要在控件内画一个居中的图标正确做法是contentsRect().center()而不是rect().center()。如果这个控件被 QSS 设了paddingcontentsRect()会自动缩进图标就不会压到边框上。注意geometry()在控件还没经过布局激活layout activate之前返回的是“你上次设置的值”不一定是最终值。想在布局算完之后拿到真实尺寸用resizeEvent或showEvent或者在布局上挂一个QTimer::singleShot(0, ...)延后读取。2.2 有布局的时候手动 move/resize 为什么无效我在网上看到最多的一类提问是“我在构造函数里resize(800, 600)为什么窗口打开还是默认大小”或者“我给子控件move(100, 100)它老老实实待在左上角。”原因很简单只要控件被加进了布局它的几何就由布局接管你手动设的会被布局在下次activate()时覆盖回去。布局的工作方式是在父控件的resizeEvent里重新计算每个子项的位置和大小然后调用setGeometry()。你的move()发生在布局算完之后看着像是生效了但父控件一被拖动、一被缩放立刻打回原形。那想让某个控件有特殊位置怎么办三条路用 stretch 和 spacing 在布局内部调。绝大多数“我想让它靠右一点”的需求addStretch()就能解决不需要脱离布局。套一层容器。把需要精确定位的控件放进一个固定的 QWidget 里让它在容器内自由move容器本身参与外层布局。干脆不用布局父控件上手动摆放所有子控件然后自己写resizeEvent做等比缩放。这条路只在做固定比例的画布类控件时才值得走。判断一个控件有没有被布局管可以看parentWidget()-layout()是否为空或者更直接地——打印w-geometry()然后 resize 父窗口再打印一次如果值变了说明布局在管它。2.3 正确做法新建 QWidget 当 central widget布局设到它身上这一条几乎每个 Qt 项目都会撞上而且错误写法特别容易通过编译。QMainWindow自带了一层内部布局用来摆放菜单栏、工具栏、停靠窗口和状态栏。你在它身上直接setLayout()Qt 会打印一行警告说不能给已经有布局的控件再设布局然后什么都没发生。我见过有人在QMainWindow子类的构造函数里写setLayout(new QVBoxLayout)跑起来界面全空debug 半天。标准写法就三步MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { auto *central new QWidget(this); // 1. 新建一个普通 QWidget auto *vbox new QVBoxLayout(central); // 2. 布局挂到它身上 vbox-setContentsMargins(8, 8, 8, 8); vbox-setSpacing(6); vbox-addWidget(new QLabel(tr(内容区), central)); setCentralWidget(central); // 3. 把它交给 QMainWindow }为什么非得绕这一圈因为setCentralWidget()内部会把整个 central widget 塞进 QMainWindow 自己的布局并且自动处理菜单栏/状态栏占用的空间。你直接设布局等于想跟 QMainWindow 的内部布局抢位置它不可能让。顺序上还有个小细节setContentsMargins要在addWidget之前或之后设都行但如果你打算改spacing最好在添加子项之前设好避免添加过程中反复触发invalidate()。虽然对性能影响微乎其微但在动态增删几十个控件的场景里这点开销会累积。2.4 尺寸三兄弟sizeHint、minimumSize、sizePolicy新手常常以为给控件设了resize(300, 200)就固定了大小实际上只要它在布局里布局更愿意听sizePolicy和sizeHint的。sizeHint()是控件“我希望多大”由控件自身实现决定比如 QPushButton 会按文字宽度加内边距算minimumSizeHint()是“我最小能多小”布局不会把控件压到比它还小除非你显式设了minimumSize那以显式的为准sizePolicy则告诉布局在多余空间里你是想长大、想保持还是随便。sizePolicy 取值含义典型场景Fixed只认sizeHint不缩不胀图标按钮、固定尺寸的指示灯Minimum可以变大但不小于sizeHint按钮、复选框Maximum可以变小但不超过sizeHint一般不单独用PreferredsizeHint是最佳值可缩可胀默认大多数控件Expanding尽量占满剩余空间文本编辑区、绘图区、列表MinimumExpanding不小于sizeHint同时尽量占满需要保底尺寸的绘图区Ignored完全忽略sizeHint占满所有给的空间背景容器两个方向要分别设setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)表示横向能胀、纵向固定这个组合在做“一行撑满、高度固定”的工具条时特别常用。还有一个从 Qt 5.2 开始才有的属性值得记住QSizePolicy::setRetainSizeWhenHidden(true)。默认情况下控件hide()之后布局会把它的空间收回去界面会跳一下打开这个开关隐藏时空间仍然保留做那种“点一下切换显示某块区域”的界面时能避免整页元素位移。实操心得我调布局时习惯打开一个临时的调试开关按下快捷键就递归打印整棵控件树的geometry和sizePolicy。比对着界面猜快得多尤其是布局嵌套三四层的时候。3. 外观类属性palette、font、QSS 的三角关系3.1 palette 设了颜色不生效的三个真实原因setPalette()是最常被吐槽“没反应”的 API。我总结下来就三个原因按出现频率排。原因一没开autoFillBackground。普通 QWidget 默认是不画背景的它只是把父控件已经画好的内容“透”出来。你给 palette 里QPalette::Window设了颜色但没人去画它。解决办法是setAutoFillBackground(true)这时候 QWidget 会在paintEvent里用 palette 的 Window 角色填充背景。QPalette pal palette(); pal.setColor(QPalette::Window, QColor(#2b2b2b)); pal.setColor(QPalette::WindowText, QColor(#e6e6e6)); setAutoFillBackground(true); // 这一行漏了上面全白干 setPalette(pal);原因二QSS 覆盖。前面说过QSS 在 polish 阶段会把background-color映射进 palette。只要任意一个祖先控件设了全局样式表且里面有匹配当前控件的规则你的 palette 就被顶掉了。判断方法很简单把qApp-setStyleSheet()临时清掉再看。原因三角色ColorRole用错了。QLineEdit这类输入控件的背景用的是QPalette::Base文字是QPalette::Text按钮类用Button和ButtonText被选中项的高亮用Highlight和HighlightedText。你给 QLineEdit 设Window角色它当然不理你。这个坑我见过不止一次尤其是给表格设行背景的时候得用Base而不是Window。还有一个容易被忽略的维度ColorGroup。QPalette 里有Active、Inactive、Disabled三组控件失去焦点进入非激活态时会切到Inactive组被setEnabled(false)后切到Disabled组。如果你只设了Active窗口一失焦颜色就变了看着像 bug。稳妥的写法是三个组一起设QPalette pal palette(); pal.setColor(QPalette::Active, QPalette::WindowText, Qt::black); pal.setColor(QPalette::Inactive, QPalette::WindowText, Qt::black); pal.setColor(QPalette::Disabled, QPalette::WindowText, QColor(#9a9a9a));3.2 font 的继承机制与高频调用的代价QWidget::setFont()有个很多人不知道的行为它会向下传播。给一个父控件设字体所有没有显式设过字体的子控件都会跟着变。这是个很好用的特性你可以只在顶层窗口设一次字体整棵控件树统一。但传播是有代价的。字体变化会引起两件事一是所有受影响控件收到QEvent::FontChange二是文字尺寸变了sizeHint随之变化布局必须重新算一遍。所以在一个有几百个控件的界面上循环给每个控件setFont()性能会明显掉下来。正确姿势有两种只在顶层设一次让继承机制干活。前提是子控件没有单独设过字体。需要批量改字体时用QApplication::setFont()全局设它只做一次字体解析和一次全局刷新。顺便说一句字体大小和 DPI 的关系。QSS 里写font-size: 14px在高 DPI 屏幕上会被当成设备无关像素处理配合 Qt 5.6 之后的高 DPI 缩放能正确放大但如果关掉了高 DPI 缩放px 就是物理像素4K 屏上会小到看不清。团队协作时我更倾向统一用pt语义更稳定。3.3 attribute 不是属性但决定绘制行为QWidget::setAttribute()操作的是Qt::WidgetAttribute枚举和Q_PROPERTY完全是两套东西但它们经常要配合使用所以放在这里一起讲。几个高频的Qt::WA_DeleteOnClose关闭窗口时自动 delete。适合弹出式对话框避免内存泄漏。注意开了它就不要再持有裸指针了。Qt::WA_TransparentForMouseEvents鼠标事件穿透。做水印、遮罩层时非常有用让鼠标能点到下层控件。Qt::WA_Hover打开后控件才会在鼠标悬停时收到QEvent::HoverEnter、HoverLeaveQSS 里的:hover伪状态也依赖它。Qt::WA_StyledBackground告诉 QWidget 它的背景应该由 QSS 来画。自定义控件继承 QWidget 之后 QSS 的background-color不生效根源就在这。Qt::WA_OpaquePaintEvent声明你会在paintEvent里画满整个矩形Qt 就不必先擦一遍背景。做高频刷新的自绘控件时能省不少开销但如果你其实没画满会出现残影。关于WA_StyledBackground和自定义控件的 QSS 问题这里给一个可以直接抄的写法void MyWidget::paintEvent(QPaintEvent *) { QStyleOption opt; opt.initFrom(this); QPainter p(this); // 让样式系统按 QSS 规则画一次背景QWidget 子类必须自己加这段 style()-drawPrimitive(QStyle::PE_Widget, opt, p, this); // 下面接自己的绘制逻辑 p.setRenderHint(QPainter::Antialiasing, true); p.drawLine(0, height() / 2, width(), height() / 2); }这段代码的价值在于只要加上它你的自定义控件就能被 QSS 的background-color、border、border-radius接管样式统一交给设计给的样式表不用再在代码里硬编码颜色。4. 状态与交互类属性可见性、焦点、鼠标、窗口标志4.1 isVisible 和 isHidden 不是反义词很多人认为isVisible()和isHidden()是一对反义词!isVisible()就等于isHidden()这是错的。准确的关系是isVisible()为真要求控件自己和它所有的祖先都处于显示状态并且控件所在的顶层窗口已经被show()。isHidden()为真只表示这个控件自己被显式隐藏了也就是调用过hide()或者构造时带了Qt::WindowFlags里的隐藏标志。所以一个子控件在它自己没被隐藏、但父窗口还没 show 的情况下isVisible()是 falseisHidden()也是 false。这个区别在做“记住上次关闭时哪些面板是展开的”这类功能时非常关键——恢复状态要用setVisible(true/false)而不是setHidden()因为前者会清掉内部的显式隐藏标志后者只是叠加一层。还有一个相关的QWidget::show()和setVisible(true)是等价的但show()是个槽可以直接连信号写起来更顺手。4.2 focusPolicy 与 mouseTracking事件收不到的先查这两个“我的控件收不到鼠标移动事件”——十次有九次是mouseTracking没开。QWidget 默认mouseTracking false这时候只有当鼠标按键被按下时才会产生mouseMoveEvent。想做出悬停高亮、跟随鼠标的十字线这类效果必须setMouseTracking(true); // 不开这个mouseMoveEvent 只在按住键时才触发 setFocusPolicy(Qt::StrongFocus); // 需要键盘输入时才需要focusPolicy同理默认值对大多数控件来说是Qt::NoFocus或者Qt::StrongFocus取决于具体控件。如果你写了个自定义控件发现点击它之后按键盘没反应先检查这一项。focusPolicy 取值行为NoFocus不接受焦点不响应 Tab 和点击TabFocus只响应 Tab 键ClickFocus只响应鼠标点击StrongFocusTab 和点击都响应最常用WheelFocus在 StrongFocus 基础上加上滚轮顺便提醒一个交互陷阱StrongFocus的控件在滚轮滚动时如果吞掉了wheelEvent用户滚页面时会“卡”在它上面。列表和下拉框是重灾区我一般会在不需要滚轮调整数值的控件上直接覆盖wheelEvent并调用event-ignore()把事件还给父级滚动区域。4.3 windowFlags 的坑改完必须 show 一次setWindowFlags()有个官方文档里写明、但极容易被忽略的副作用它内部会调用setParent()导致控件被隐藏。所以如果你在已经显示的窗口上动态改标志比如从普通窗口切成无边框、或者加一个Qt::Tool让它不占任务栏改完必须再show()一次否则窗口直接消失。// 切换无边框模式 setWindowFlags(windowFlags() | Qt::FramelessWindowHint); show(); // 漏掉这一行窗口就没了还有一个组合坑Qt::FramelessWindowHint加上Qt::WA_TranslucentBackground才能做出真正的圆角或异形窗口。只设前者不设后者窗口四角是黑的只设后者不设前者背景透明了但系统边框还在。两个都要顺序上先设 attribute 再设 flags 更稳妥。另外setWindowFlags会让窗口位置重置做无边框窗口拖动时记得在切换前后保存和恢复pos()。5. 属性批量设置与动态属性省时间也省性能5.1 用 QMetaObject 遍历批量设置属性当配置项来自 JSON、ini 或者服务端下发的时候一个个if-else去写setXxx()会疯掉。Qt 的元对象系统在这里能省很多事——只要属性是用Q_PROPERTY声明过的就能通过名字反射写入。void applyProperties(QWidget *w, const QVariantMap values) { if (!w) return; w-setUpdatesEnabled(false); // 批量期间挂起重绘 const QMetaObject *mo w-metaObject(); for (auto it values.constBegin(); it ! values.constEnd(); it) { const QByteArray name it.key().toUtf8(); const int idx mo-indexOfProperty(name.constData()); if (idx 0) { // 属性不存在退化成动态属性可以给 QSS 选择器用 w-setProperty(name.constData(), it.value()); continue; } const QMetaProperty mp mo-property(idx); if (!mp.isWritable()) continue; // 只读属性直接跳过避免无效写入 if (!mp.write(w, it.value())) { qWarning() 属性写入失败 name it.value(); } } w-setUpdatesEnabled(true); // 恢复并触发一次重绘 w-update(); }有几点值得展开说。indexOfProperty找不到时返回 -1这时候调setProperty不会报错而是创建一个动态属性。这个行为很多人不知道会误以为“属性没声明也能设那 Q_PROPERTY 有什么用”。区别在于动态属性没有 getter/setter不参与信号通知也不能在 Designer 里编辑只能被property()读回来或者被 QSS 当作选择器条件。isWritable()这一层过滤很有必要。有些属性只在构造时能写比如某些只读的QRect派生属性不加判断会得到一堆运行期警告日志里全是噪音。Python 侧的对应写法PyQt5/PySide2 也一样用setattr会比 C 更简洁但少了类型检查写错属性名不会报错只会多一个动态属性这也是为什么我一直建议在 Python 项目里维护一份属性白名单宁可多写几行也别让拼错的键名悄悄溜过去。5.2 动态属性和 QSS 选择器的联动动态属性最实用的场景是和 QSS 配合做“同一类控件的不同状态样式”。比如给一批按钮标记优先级btn-setProperty(level, danger);配合样式表QPushButton[leveldanger] { background-color: #d9534f; color: white; } QPushButton[levelnormal] { background-color: #f0f0f0; }这里有个必须记住的点动态属性改变之后QSS 不会自动重新匹配。首次显示时会正确应用但你运行期改属性值界面不会变。需要手动触发一次样式重算btn-setProperty(level, danger); btn-style()-unpolish(btn); // 先卸掉旧样式 btn-style()-polish(btn); // 再按新属性匹配一次 btn-update();或者在控件上重写changeEvent()捕获QEvent::DynamicPropertyChange后自动做 unpolish/polish。在一个基类里做一次全项目受益这是我比较推荐的工程做法。5.3 批量设置的性能守则setUpdatesEnabled 与构造期赋值前面代码里用的setUpdatesEnabled(false)是批量改属性的核心技巧。它的原理是挂起期间所有update()请求被合并丢弃重新启用时只做一次完整重绘而不是改一个属性就重绘一次。但要注意它不能拦住几何变化。setUpdatesEnabled(false)期间你调resize()控件的实际尺寸还是会变只是不画。所以做那种“一次性重排几十个控件”的场景更彻底的做法是把布局也一起挂起parent-setUpdatesEnabled(false); parent-layout()-setEnabled(false); // 暂停布局计算 // ... 大量修改尺寸、边距、添加删除子控件 ... parent-layout()-activate(); // 手动激活一次 parent-layout()-setEnabled(true); parent-setUpdatesEnabled(true);这条链路在动态生成表格式界面比如几十行几十列的属性编辑器时能把刷新时间从秒级压到毫秒级。另一条更省事的守则能在构造期设的属性就别拖到 show 之后。构造函数里控件还没被布局管理、还没映射到屏幕setMinimumSize、setSizePolicy、setContentsMargins这些操作基本只是赋值加标记 dirty代价极低。等控件显示出来再改同样的东西就要走一遍布局重算加重绘。注意setUpdatesEnabled(false)之后一定要确保有对应的true异常路径也要覆盖到。我习惯用QScopedValueRollback或者一个小的 RAII 包装类来保证恢复避免某次提前 return 之后整个界面再也不刷新。6. 属性不生效的排查清单与实战问答6.1 一张速查表覆盖八成问题现象最可能的原因快速验证setPalette背景没变autoFillBackground未开 或 QSS 覆盖临时清空 styleSheet 再试palette 文字色失焦后变了只设了Active组补设Inactive/Disabled组自定义控件 QSS 背景不生效缺WA_StyledBackground或没重写 paintEvent加QStyle::PE_Widget绘制resize不起作用控件在布局里被布局覆盖查parentWidget()-layout()move之后又弹回原位同上同上mouseMoveEvent不触发mouseTracking为 falsesetMouseTracking(true)键盘输入没反应focusPolicy是NoFocus打印focusPolicy()改 windowFlags 后窗口消失setWindowFlags 隐藏了窗口补一次show()动态属性改了界面没变QSS 未重新 polish手动 unpolish/polish控件隐藏后界面跳动sizePolicy 未保留隐藏尺寸setRetainSizeWhenHidden(true)6.2 三个我踩过的真实坑第一个坑在QMainWindow上直接设布局。当时刚接触 Qt照着教程写setLayout(new QVBoxLayout)编译通过、运行没报错、界面一片空白。后来才明白 QMainWindow 内部已经有布局setLayout会被拒绝执行。从那以后我见到新项目的 MainWindow 都先看它有没有 central widget。第二个坑给 QSS 设了border-radius控件依然是方的。排查了半天才发现那个控件是继承自 QWidget 的自定义类QSS 对 QWidget 子类的背景和边框支持需要WA_StyledBackground。加上属性之后立刻正常。那次之后我在所有自绘基类的构造函数里都加了这一行并写成注释说明原因。第三个坑批量设字体导致界面卡顿。一个动态生成的列表每行八个控件一共两百行构造时逐个setFont。首次显示要等将近两秒。改成只在列表容器上设一次字体、靠继承传播之后时间掉到几十毫秒。这件事让我彻底记住了字体传播这个机制的存在。6.3 排查工具与调试手法除了最朴素的qDebug()打印有几个手段我经常用。一是QT_STYLE_OVERRIDE环境变量临时把样式切到 Fusion判断问题是不是出在平台原生样式上。有些属性在 Windows 原生样式下行为和在 Fusion 下不一样特别是按钮的尺寸和边距。二是 QSS 二分法。样式表越写越长之后某个控件样式不对最有效的办法是把它单独摘出来只留最基础的一条规则确认生效后再一条条加回去。别试图通读整个样式表找冲突规则一多靠眼睛看出优先级问题几乎不可能。三是调试属性和 QSS 的先后顺序时在showEvent里打印。因为 QSS 的 polish 发生在控件被添加进父级和首次显示的过程中构造阶段打印的 palette 是设置前的值容易误导。在showEvent里打印palette().color(QPalette::Window)才是最终生效的值。四是看待办列表里的event-ignore()。属性设置不生效的一大类原因是事件在到达目标控件之前被上层吞了。遇到“明明属性都对、就是不响应”的情况可以装一个全局事件过滤器打印事件类型和接收对象往往能发现是某个父级控件把事件拦下了。如果你手头正好有个界面调了半天不对劲我的建议是先把这篇里的速查表过一遍八成能定位到问题。剩下两成多半是属性的设置时机不对——要么设早了被setupUi覆盖要么设晚了被 QSS 顶掉。我个人在项目里的做法是把几何属性全部收进构造函数和一个专门的setupGeometry()私有方法外观属性全部交给一份统一的 QSS 文件代码里只保留动态属性作为状态钩子。这条界线划清之后样式和布局的调试成本会低一个量级团队里其他人接手时也不用再猜某个颜色到底是哪儿设的。
返回列表