资讯动态

QWidget核心解析:从Qt控件体系到工程实践

发布时间:2026/10/14 4:13:21 来源:尧图企业网站定制
很多从零学 Qt 的朋友第一次看到控件相关的内容都会被那一大串类名砸得晕头转向QPushButton、QLabel、QLineEdit、QComboBox……每个类都有自己的构造函数、专属方法、信号和槽好像要记的东西无穷无尽。但如果你把这些类横向铺开再纵向往上看就会发现在所有可视控件的顶层始终站着一个叫 QWidget 的基类。只要把它吃透后面学任何控件都会轻松很多。这篇不是教科书式的罗列是我按照自己实际做 Qt 项目的经验把 QWidget 里最常用、最容易被忽视的内容拆开揉碎穿插代码和踩坑记录。内容面向刚接触 Qt 的开发者也适合写过一些 Demo 但想系统补一遍基础的朋友。别急我们从一个最原始的窗口开始。1. 先搞清楚 QWidget 在整个 Qt 控件体系里的位置1.1 从继承树看控件家族Qt 的控件体系其实是一棵非常规则的树。所有窗口和控件都直接或间接继承自 QWidget而 QWidget 往上还有两个父类QObject 和 QPaintDevice。QObject 给 QWidget 带来了信号槽机制、对象树父子关系、属性系统、定时器支持。也就是说每个控件都能发信号、连槽函数能被统一地以父子形式管理。QPaintDevice 则是所有可绘制对象的基类它意味着 QWidget 天然就拥有被 QPainter 绘制的资格这也是为什么我们随便拿一个控件都能在上面 paintEvent 画图。再看 QWidget 的下游几大常见分支QFrame 派生出了 QLabel、QLabel 的兄弟还有 QLCDNumber、QSplitter 等它们共享边框和凹槽样式。QAbstractButton 派生出了 QPushButton、QToolButton、QCheckBox、QRadioButton。QAbstractSpinBox 派生出了 QSpinBox、QDoubleSpinBox。QAbstractSlider 派生出了 QSlider、QScrollBar、QDial。QComboBox 虽然看起来复杂但也只是 QWidget 的直系后代。理解这层树之后你会发现一个很实用的结论凡是继承自 QAbstractButton 的控件都支持 setText、setIcon、setCheckable凡是继承自 QFrame 的控件都支持 setFrameStyle。很多时候要实现的“统一风格”根本不用逐个控件去设置只要找到它们共同的祖先类遍历设置一遍即可。1.2 QWidget 本身也是一个可以独立使用的窗口很多人误以为 QWidget 只是个“抽象的控件基类”不能单独拿来当窗口。实际上恰恰相反QWidget 自己就能干所有窗口该干的事设置标题、调整大小、最大化最小化、响应事件、承载子控件。我早期写小工具的时候就喜欢直接用 QWidget 做顶层窗口而不是非要用 QMainWindow 或 QDialog#include QApplication #include QWidget int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget w; w.setWindowTitle(QStringLiteral(独立QWidget窗口)); w.resize(480, 320); w.show(); return app.exec(); }这段代码就是最纯粹的 Qt 窗口程序。它没有菜单栏、没有状态栏但功能完整。特别适合做弹窗、工具面板、悬浮小窗这类场景。而 QMainWindow 说白了是在 QWidget 基础上扩展了菜单栏、工具栏、状态栏、中央控件区centralWidget 也是一个 QWidgetQDialog 则在 QWidget 基础上增加了与用户模态交互相关的行为。所以严格来说QMainWindow 和 QDialog 都“是一种 QWidget”。1.3 parent 参数为什么那么重要QWidget 构造函数里有一个 QWidget *parent nullptr 参数。这个参数决定了两件事一是控件的显示位置二是控件的生命周期。看这段代码QWidget *parentWindow new QWidget; parentWindow-setWindowTitle(QStringLiteral(父窗口)); parentWindow-resize(600, 400); QPushButton *btn new QPushButton(QStringLiteral(我是子控件), parentWindow); btn-move(50, 50); parentWindow-show();按钮传入了 parentWindow 作为父对象所以它会显示在父窗口内部坐标 (50,50) 也是相对父窗口客户区左上角的。如果这里不传 parent新建的按钮就会变成一个独立顶层窗口标题为“”位置由桌面坐标系决定非常容易让人摸不着头脑。另外Qt 的对象树机制规定父对象在析构时会负责 delete 所有子对象。所以只要 new 出来的控件设置了合理的 parent你基本不用手动 delete。反过来如果 new 出控件却忘了设 parent它将在堆上泄漏。这也是我每次写完 new QWidget 后必查的第一件事parent 到底传没传。2. 用代码把第一个 QWidget 跑起来2.1 最简 main 函数里发生了什么上面的 main 函数虽然短但每一行都值得展开说。QApplication 是 Qt GUI 应用的唯一应用对象它负责初始化窗口系统资源、处理事件队列、管理剪贴板等全局功能。一个 Qt GUI 程序只能创建一个 QApplication或 QGuiApplication / QCoreApplication必须在创建任何控件之前创建。QApplication app(argc, argv);这行执行后Qt 就已经拿到操作系统的显示句柄也知道当前是什么窗口系统。紧接着QWidget w;这是在栈上直接创建控件。注意栈对象不需要手动 delete程序退出时自动析构这是最安全的写法。如果要在堆上创建建议记住“要么给 parent要么用智能指针要么等窗口关闭后主动 delete”。w.show();这里有个新手很容易忽略的点创建出来的控件默认是隐藏的。QWidget 构造完成后visible 属性是 false。你必须调用 show()等价于 setVisible(true)才能把它显示出来。如果不 show程序依然会正常执行但屏幕上什么都没有。return app.exec();exec() 启动了 Qt 的事件循环。事件循环一旦启动程序就会在这个函数里一直转直到最后一个窗口被关闭、quit() 被调用。所有鼠标、键盘、定时器、重绘、网络事件都会经过这个循环分发。事件循环结束并返回后main 函数退出程序结束。2.2 在空白 QWidget 上添加一个按钮没有布局的时候往空白 QWidget 上放子控件就是这么直白QWidget *container new QWidget; container-resize(400, 300); QPushButton *button new QPushButton(container); button-setText(QStringLiteral(按钮)); button-move(100, 100); button-resize(120, 40); container-show();这里 button 创建时传了 container 作为 parent。随后 move 指定按钮左上角相对于 container 的位置resize 指定按钮尺寸。这套坐标系统对初学者比较友好子控件永远以父控件客户区左上角为原点向右为 X 正方向向下为 Y 正方向。QWidget 没有自带的边框偏移子控件的坐标系直接对应父控件的客户区。但这种“手工摆放”方式在窗口尺寸变化后就会暴露出问题窗口拉大时按钮还贴在原地窗口缩小时按钮甚至可能被裁掉。所以后续我们会引入布局管理器来解决它。2.3 空 QWidget 为什么能用来做自定义控件有时候我们需要一个具备独立功能、能响应事件、可绘制的矩形区域但又不希望它带任何现成控件的样式。最方便的做法就是直接继承 QWidget。比如说做一个“色块选择区域”不需要 QLabel、不需要 QFrame只要一块能画几个颜色方块、能点击的区域。这时可以用一个自定义类class ColorBlock : public QWidget { Q_OBJECT public: explicit ColorBlock(QWidget *parent nullptr) : QWidget(parent) {} };然后把这个 ColorBlock 放入某个布局里它就是一个可参与排列、可被样式表控制、可重写鼠标事件的“空白画布”。这正是 QWidget 作为基类最有价值的地方它不限制你也不帮你画任何既定的东西一切由你自己决定。3. QWidget 的常用属性你会用到的那些 set 方法3.1 窗口标题、尺寸与位置QWidget 提供了一套成体系的 set 方法。最基础的有几个setWindowTitle(const QString title)设置窗口标题只对顶层窗口有效。如果你对一个嵌入布局里的子控件调用 setWindowTitle它是不会显示的。resize(int w, int h) 或 resize(const QSize size)设置窗口/控件的尺寸不包括边框仅指客户区。move(int x, int y) 或 move(const QPoint pos)设置位置。对顶层窗口来说坐标相对屏幕对子控件来说坐标相对父控件的客户区。setGeometry(int x, int y, int w, int h) 或 setGeometry(const QRect rect)同时设置位置和尺寸。从工程实测看setGeometry 更常用于配合固定布局逻辑比如让一个 QWidget 恰好填满另一个 QWidget 的某一区域。注意 setGeometry 设置的位置是基于父客户区的如果你在 main 函数里给 QWidget 设置 setGeometry(100, 100, 800, 600)这个窗口会出现在屏幕坐标 (100,100) 处而不是相对某个父控件。这里有一个我曾经踩过的坑在自定义控件里重写了 resizeEvent然后又在 resizeEvent 里对某个子控件调用 setGeometry。如果递归逻辑没做好会出现每次窗口改变尺寸时子控件抖动、甚至无限循环的迹象。原因是 resize() 会触发 resizeEventresizeEvent 里又调用 setGeometrysetGeometry 又改变了子控件尺寸可能再次触发父控件的重排。解决办法是加标志位或者避免在 resizeEvent 里做过于复杂的同步。3.2 尺寸约束最小、最大、固定、自适应实际项目里窗口尺寸不是随意的。比如登录弹窗如果允许用户拉得很大背景图片会被拉变形某些工具窗口又希望固定尺寸不允许拉伸。QWidget 提供了以下约束方法方法效果典型场景setMinimumSize(w, h)窗口尺寸不能小于给定值防止布局控件被压缩setMaximumSize(w, h)窗口尺寸不能大于给定值限制图片预览窗口不会过大setFixedSize(w, h)同时把最小和最大设为同一值不可调整大小固定登录框/输入框setSizeIncrement(w, h)按步进方式调整大小特殊工具窗口adjustSize()根据子控件 sizeHint 自动调整尺寸控件初始化后快速自适应这里有一个关键点这些约束不仅影响用户手动拉伸窗口也影响布局管理器对子控件分配的空间。我在做一个多平台适配工具时给某个 QWidget 设置了 setMinimumSize结果布局管理器在窗口变小时永远保留这块空间导致其他区域出现空白。所以要分清楚minimumSize 是硬约束布局管理器必须满足它即使当前窗口已经小到装不下。如果你想禁止用户拉伸窗口又不想写死尺寸可以设置 setMaximumSize 等于 setMinimumSize也比暴露 resize 事件灵活。3.3 启用状态、可见性、窗口标志大多数控件默认 enabled 为 true。当你调用 setEnabled(false) 时控件会变成灰色默认样式下并且不再响应鼠标点击、键盘输入。这在做权限控制、数据未加载完成时非常常用。注意 setEnabled 也有子组件传递效应禁用父窗口后子控件全部跟着禁用重新启用父窗口后子控件自动恢复原来的启用状态。可见性方面setVisible(bool) 是最高层接口而 show() 和 hide() 只是 setVisible(true/false) 的便捷封装。这里有个细节当你首次 show() 一个控件时它会触发 showEvent随后 hide() 触发 hideEvent。如果你在 showEvent 里做一些初始化会受这种调用顺序影响。窗口标志决定一个 QWidget 在窗口系统眼里是什么身份。常见组合w.setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool);Qt::FramelessWindowHint去掉系统边框和标题栏适合自绘启动画面、悬浮提示。Qt::WindowStaysOnTopHint置顶。Qt::Tool让窗口不出现在任务栏适合辅助面板。Qt::WindowMinimizeButtonHint / Qt::WindowMaximizeButtonHint控制最小化/最大化按钮的显示。窗口标志可以在运行时更改但某些系统下需要重新 show() 才能完全生效这是我实测后总结的经验。改完 setWindowFlags 后最好紧跟一句 setVisible(true)否则在部分平台下窗口不刷新。还有两个经常成对出现的属性w.setAttribute(Qt::WA_DeleteOnClose);设置之后当这个 QWidget 收到 closeEvent 且被 accept 时Qt 会直接 delete 该对象。对于 new 出来的顶层窗口这是最简单的防内存泄漏方式。注意不要对栈对象设置 WA_DeleteOnClose否则程序退出时会 double delete。3.4 其他高频小属性对象名、提示、光标、焦点策略setObjectName 看起来不起眼却是 QSS 和 findChild 的基石。QSS 里写#myButton { color: red; }就是靠 objectName 匹配的。findChildQPushButton*(myButton) 也需要 objectName。所以我在工程里强烈建议给每个关键控件设置语义化 objectName比如 save_btn、clear_btn这会让后续维护样式的成本低很多。setToolTip 设置悬停提示setToolTipDuration 控制显示时长。setCursor 可以改变鼠标在控件上时的光标形状比如给可点击的卡片设置 Qt::PointingHandCursor效果和网页里的 pointer 一致。焦点策略 setFocusPolicy 决定控件能否通过键盘 Tab 切换焦点、能否被点击获得焦点。一个按钮默认为 Qt::StrongFocus也就是既可以通过 Tab 聚焦也可以点击聚焦。如果希望点击按钮时不抢走当前输入框的焦点可以设为 Qt::NoFocus这在处理外部键盘输入审核时很关键。4. 重写事件函数让 QWidget 真正“自定义”4.1 事件从哪来事件循环与 event() 分发QWidget 的事件处理是一套完整的分发链。系统产生一个鼠标点击后窗口系统把消息交给 Qt 的事件循环Qt 把它包装成 QMouseEvent调用全局的事件投递最终找到对应的 QWidget先调用该对象的 event(QEvent*)然后再由 event() 根据事件类型分发给具体的事件函数如 mousePressEvent、paintEvent。我们可以重写 event() 来拦截所有事件也可以只重写某一个事件函数。正常业务里重写具体 handler 就够了。比如想要实现“点击窗口空白处关闭”就重写 mousePressEvent判断 event-button() 为左键后调用 close()。4.2 用 paintEvent 在 QWidget 上画一个圆角矩形在 QWidget 上画画是它的核心能力之一。假设我们要自定义一个卡片控件画一个圆角矩形背景#include QPainter class CardWidget : public QWidget { Q_OBJECT public: explicit CardWidget(QWidget *parent nullptr) : QWidget(parent) {} protected: void paintEvent(QPaintEvent *) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setBrush(QColor(240, 244, 248)); painter.setPen(Qt::NoPen); painter.drawRoundedRect(rect().adjusted(1, 1, -1, -1), 12, 12); } };这里有几个关键点必须在 paintEvent 内部创建 QPainter而不是在构造函数里创建。因为 QWidget 的绘制是随时可能被系统重绘的比如窗口移动、尺寸变化、被其他窗口遮挡后暴露都会触发 paintEvent。你必须在每次重绘时重新绘制构造函数里画一次是不够的。painter(this) 会以当前控件为绘制设备坐标系就是控件的本地坐标。rect() 返回当前控件的客户区矩形。drawRoundedRect 绘制圆角矩形时如果不做 adjusted边缘可能会被裁掉 1 像素视觉效果发虚所以我习惯向内缩 1 像素。QPainter 的双缓冲由 Qt 自动处理你不需要手动清屏下一帧会重新画。注意如果你不重写 paintEventQWidget 默认不绘制任何背景。这也是为什么直接 new 一个 QWidget 然后丢进布局里它看起来是透明/黑色的原因。4.3 重写鼠标事件实现无边框窗口拖动无边框窗口FramelessWindowHint最大的问题是不能用系统标题栏拖动。最常见的解决方案就是重写 mousePressEvent 和 mouseMoveEvent。我常用一种简洁写法void FramelessWidget::mousePressEvent(QMouseEvent *event) { if (event-button() Qt::LeftButton) { m_dragPosition event-globalPosition().toPoint() - frameGeometry().topLeft(); event-accept(); } } void FramelessWidget::mouseMoveEvent(QMouseEvent *event) { if (event-buttons() Qt::LeftButton) { move(event-globalPosition().toPoint() - m_dragPosition); event-accept(); } }这里 m_dragPosition 是 QPoint 成员变量。核心逻辑在鼠标按下时记录窗口左上角在屏幕上的位置与全局鼠标位置的差值鼠标移动时再用新的全局位置减掉差值得到新的窗口左上角坐标。注意 event-globalPosition() 是 Qt 6 推荐的写法在 Qt 5 里是 event-globalPos()。另外鼠标移动事件只有在按下按钮时才持续触发所以要判断 event-buttons() 是否为左键而不是 button()。button() 只表示最后一次按钮变化的那个键。处理完事件后调用 event-accept() 很关键。如果不 acceptQt 会把事件往上传递给父控件可能造成父级也响应这次拖动导致抖动或二次移动。4.4 重写 closeEvent 时最容易踩的坑QWidget::closeEvent 用来控制窗口关闭行为。很多人会这么写void MyWidget::closeEvent(QCloseEvent *event) { if (m_unsaved) { event-ignore(); return; } // 这里忘了调用 event-accept() }如果分支里做完保存后直接 return而没有显式调用 event-accept()窗口很可能不会关闭。因为 QCloseEvent 默认是 accepted 的但如果你重写 closeEvent 但忘了调用 accept在一些平台下它会保持默认状态。实际上 Qt 文档建议除非你调用 ignore()否则事件默认会继续传播并关闭窗口。也就是说重写后只要调用了 ignore()就要确保所有正常路径都执行 accept()。最稳妥的写法是if (m_unsaved) { event-ignore(); } else { event-accept(); }养成习惯不要依赖默认行为。5. 用布局把子控件组织起来而不是手动 move5.1 手动坐标的痛点前面我们用 move() 摆放子控件这种方案在静态窗口里没有问题但只要窗口尺寸一变手动坐标的控件的形态就会崩。你不可能为每一个窗口尺寸写一堆判断坐标的代码。Qt 提供的布局管理器QLayout 体系就是为了解决这个问题。布局管理的核心逻辑是当父 QWidget 尺寸变化时布局器根据约束条件重新计算每个子控件的位置和尺寸。子控件不需要你手动指定几何信息只要设置好 sizeHint 和策略其实大部分控件都自带合理默认值布局器会自动完成一切。5.2 三大布局垂直、水平、网格Qt 最常用的三种布局类是QVBoxLayout垂直排列控件自上而下。QHBoxLayout水平排列控件从左到右。QGridLayout网格排列可以跨行跨列。假设我们要在一个 QWidget 上放置一个标签和两个按钮垂直排列QWidget *panel new QWidget; QLabel *label new QLabel(QStringLiteral(用户信息)); QPushButton *editBtn new QPushButton(QStringLiteral(编辑)); QPushButton *saveBtn new QPushButton(QStringLiteral(保存)); QVBoxLayout *layout new QVBoxLayout(panel); layout-addWidget(label); layout-addWidget(editBtn); layout-addWidget(saveBtn);注意 QVBoxLayout 构造函数传入 panel等价于先 new 一个 layout再 panel-setLayout(layout)。把布局传给 QWidget 后布局器会在 panel 内部管理这些控件控件会自动把 parent 设置为 panel无需手动设置。布局器有几个常见属性setContentsMargins(left, top, right, bottom)控制布局与 QWidget 边缘的间距默认值因平台而不同。如果你发现子控件贴边了多半是这里为零。setSpacing(int)设置相邻控件的间距。setStretchFactor(widget, stretch)设置某个控件在剩余空间分配时的权重权重越大分配的空间越多。5.3 嵌套布局和伸缩因子一个复杂界面往往需要多级嵌套。比如做一个表单区域左侧是标签列右侧是输入框列底部是两个操作按钮QWidget *formPanel new QWidget; QGridLayout *formLayout new QGridLayout; formLayout-addWidget(new QLabel(QStringLiteral(姓名)), 0, 0); formLayout-addWidget(new QLineEdit, 0, 1); formLayout-addWidget(new QLabel(QStringLiteral(邮箱)), 1, 0); formLayout-addWidget(new QLineEdit, 1, 1); QVBoxLayout *mainLayout new QVBoxLayout(formPanel); mainLayout-addLayout(formLayout); mainLayout-addStretch(1); QHBoxLayout *btnLayout new QHBoxLayout; btnLayout-addStretch(1); btnLayout-addWidget(new QPushButton(QStringLiteral(确定))); btnLayout-addWidget(new QPushButton(QStringLiteral(取消))); mainLayout-addLayout(btnLayout);这里 addStretch(1) 表示加入一个可伸缩的空白项权重为 1。它会推动按钮组靠右排列。嵌套布局时addLayout 把子布局当成一个整体加入父布局。实际项目中我很少直接计算子控件位置布局器配合 sizePolicy 基本能覆盖绝大多数场景。但要注意如果某个子控件需要固定宽度请设置 setFixedWidth而不是依赖布局的 stretch否则它会随窗口拉伸变形。5.4 布局与父子对象树的杂谈创建在堆上的 QLayout 不需要手动 delete。布局器在作为 QWidget 布局时会被 QWidget 接管当 QWidget 析构后布局器也会被释放。但你如果在 delete QWidget 之前单独 delete 布局器会引发崩溃因为 QWidget 还在使用它。还有一点给同一个 QWidget 连续设置两次 setLayout第一次设置的布局会被替换掉原布局里的控件不会被自动重新 parent。如果你想重新组织界面建议先删除原布局再创建新布局。6. QSS用样式表快速改变 QWidget 外观6.1 setStyleSheet 的基本姿势QWidget 支持通过 setStyleSheet 传入类 CSS 的样式表让一个普通 QWidget 瞬间变成圆角卡片、渐变背景、带阴影的容器。QWidget *card new QWidget; card-setStyleSheet(R( QWidget { background-color: #FFFFFF; border: 1px solid #D0D0D0; border-radius: 8px; } ));这行样式把整个 QWidget 的背景画成白色带浅灰边框和圆角。对我这种不喜欢写大量自绘代码的开发者来说QSS 是性能与成本之间的平衡点。QSS 选择器支持类名、objectName、后代选择器。比如card-setStyleSheet(R( #titleLabel { font-size: 16px; color: #333333; } QWidget#cardPanel { background-color: #F7F7F7; border-radius: 12px; } ));注意QWidget#cardPanel这种带 id 的写法比单纯#cardPanel更有明确性。6.2 QWidget 设置背景色不生效的经典坑很多人在 QWidget 上用样式表设置了 background-color结果整个窗口还是透明的。原因很简单普通 QWidget 默认不绘制样式表背景你需要额外调用card-setAttribute(Qt::WA_StyledBackground, true);这个属性告诉 QWidget你的背景要交给样式表绘制。如果不设置即使 QSS 里写了 background-color也不会画出来。我早期在这个坑上浪费过不少时间所以特意提出来。如果你用的是 QFrame 或 QPushButton它们本身会绘制背景就不需要这个属性。还有一种做法是干脆用 QFrame 代替纯 QWidget然后给 QFrame 设置样式表因为 QFrame 默认会绘制背景。但如果你的界面里确实需要一个不带边框语义的纯容器那么 WA_StyledBackground 就是标准答案。6.3 全局样式表与控件样式的优先级setStyleSheet 可以按控件粒度设置也可以在 QApplication 上设置全局样式表。全局样式表影响所有控件但局部样式表优先级更高。也就是说对某个按钮单独 setStyleSheet会覆盖全局样式表里对 QPushButton 的样式。在项目实践中我更倾向于把全局样式表放在一个单独的文件或 QString 常量里在 main 函数中统一加载app.setStyleSheet(globalStyleSheet);需要特化的控件再单独设置。这样做的好处是主题统一、改起来方便不会每个界面 setStyleSheet 一套最后风格散架。7. 实际项目里关于 QWidget 的几点经验7.1 别把 QWidget 用成“万能框”QWidget 虽然什么都能干但也不要为了省事把所有控件都塞进一个裸 QWidget。Qt 提供了更具体的容器类按场景选更合适需要菜单栏、工具栏、状态栏用 QMainWindow。需要模态对话框、标准按钮用 QDialog。需要分组边框用 QGroupBox。需要可绘制边框线的容器用 QFrame。需要多标签页用 QTabWidget它内部管理了 QStackedWidget 和 QTabBar。QWidget 最适合的场景是“自定义控件”和“纯容器”。比如做图表展示区、手绘区域、无边框悬浮面板直接用 QWidget 派生类即可。7.2 内存管理记住两条铁律第一new 出来的 QWidget 必须要么有 parent要么 WA_DeleteOnClose要么手动管理指针。第二条栈上的 QWidget 不要设 WA_DeleteOnClose因为对象销毁时还会执行析构再 delete 会导致崩溃。如果你写的是自定义控件class SparkLine : public QWidget { ... };在某个界面里SparkLine *spark new SparkLine(container); layout-addWidget(spark);spark 的 parent 会通过 addWidget 自动变成 container 或其内部控件所以不需要手动 delete。这也是 Qt 对象树最省心的地方。7.3 中文字面量尽量用 QStringLiteral虽然这是老生常谈但还是要强调。Qt 5 以后源码文件用 UTF-8 编码时直接写button-setText(QStringLiteral(保存));避免使用QString(保存)在部分编译器/平台下因编码差异导致的乱码。如果你在 MSVC 下不加 QStringLiteral字符串可能被当成本地代码页处理UI 上就是乱码。我一般直接强制团队规范所有 UI 字符串一律 QStringLiteral 包裹。7.4 多显示器与高DPIQt 6 默认开启高DPI缩放但在 Qt 5 中需要设置QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);而且必须在创建 QApplication 之前调用否则无效。多显示器环境下QWidget 的 geometry 可能跨到副屏。如果你要保存窗口位置不要简单保存 width() 和 height()最好结合屏幕可用区域 clamp。我遇到过一次主屏分辨率变化后以前保存的窗口坐标跑到屏幕外面去了窗口找不到。后来统一在保存前用 QScreen::availableGeometry() 做越界校验问题就解决了。回过头来想QWidget 真正教给我的不是一堆 set 方法怎么背而是 Qt 所有界面交互都围绕一个核心模型一个矩形对象有父有子能响应事件能被绘制能被布局管理。后面的具体控件无非是在这个矩形上加了不同的外观、输入逻辑和信号。把 QWidget 这部分吃透再学任何控件都会快很多。接下来我会继续写常用控件的后续章节咱们按系列一点点啃。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑