简介面向个人电脑端 C 开发者的窗口软件界面框架内置标题栏、导航栏、主界面、状态栏四段式布局支持窗口拖动与锁定、动态下拉菜单、一键切换皮肤以及导航栏在上、下、左、右多个位置间的灵活调整适合需要快速搭建美观客户端界面或想基于通用框架做二次开发的初中级开发者参考学习。资源包共两千一百七十个文件压缩后约二十三兆。文件构成以图片素材和源码为主一千九百余张 PNG 图片提供完整界面视觉元素数十个 H 头文件与 CPP 源文件承载工程逻辑另有 QSS 样式表、工程配置文件、音频文件、脚本等辅助资源便于阅读和二次构建。目前已有七千八百一十五人学习。这份资源包含可直接编译运行的完整工程与全部源码能够根据需求调整导航布局、替换皮肤主题、更换界面图片既能作为产品级软件外壳快速起步也能从实现细节中学习窗口管理、菜单动画和样式切换的 QT 工程实践。1. 切入QT界面丑不是玄学是没把通用框架和样式一次性铺好写 C 桌面工具最难受的时刻不是业务逻辑出问题而是功能做完了界面还停在灰底黑边的默认控件上。这个 QT软件界面框架PC端资源包就是把 QT通用框架 和 QT样式 打包到一起的东西目标很直接让 PC端软件 不用从零写 QSS直接套一套统一的视觉方案。它不是拿来做布局、做窗口管理的重型框架而是一堆 QSS 样式表、图标资源、演示工程和说明文档解决的是“控件丑、各窗口风格不统一、QSS 改起来乱”这三件事。适合刚写完功能没时间抠界面的开发者也适合想给团队定一套统一视觉标准、又不想从零写样式的桌面端 C 同学。下面从拆包开始说。2. 拆包看货这类 QT界面框架 包的内容构成与接入前提2.1 包里的内容大致是这几类我拆这种资源包时目录结构通常长这样具体到你手里的包可能略有出入但核心不会差太多styles/ default.qss # 全套默认样式 light.qss # 浅色主题 dark.qss # 深色主题 parts/ # 按部件拆分的样式片段 buttons.qss inputs.qss table.qss scrollbar.qss resources/ icons.png # 常用图标有的包是 .svg 或 .ico images.qrc # 资源文件索引 demo/ main.cpp mainwindow.h/cpp mainwindow.ui README.md这套结构的价值在于默认样式文件负责整体观感parts 目录让“改按钮”和“改表格”互不干扰demo 工程用来验证样式有没有破坏布局。文件路径 实际作用 什么时候用 styles/default.qss 全局控件选择器集合 作为基础样式常驻 styles/light.qss 浅色配色覆盖 有多个主题时叠加 styles/dark.qss 深色配色覆盖 夜间模式或工具类软件偏好 styles/parts/*.qss 按控件类型拆的片段 多人协作时避免改同一个大文件 demo/mainwindow.ui 演示窗口布局 验证样式效果的第一现场拿到包之后先看一眼 README 里标注的 Qt 版本。如果它在你当前环境跑出来和截图不一致优先去查样式表的解析差异而不是怀疑代码写错。2.2 为什么这类通用框架用的是 QSS而不是 QML很多刚接触 Qt 的人会问既然要做界面为什么不直接上 QML 加 Quick这个问题在选型阶段要想清楚。这个包选 QSS 的原因和它面向 PC 端 C 工具类软件的场景强相关。对比维度 QSS Widgets QML Quick 上手成本 会 CSS 基本就能改 需要学 QML 和 JS 绑定语法 侵入性 不碰业务逻辑只改外观 需要把界面层整体重写 动画能力 有限主要靠 QSS 过渡 原生支持动画和状态机 适合场景 存量业务、C 工具、PC 端 新项目、强动效、触屏应用如果你的项目已经用 Widgets 写了大量窗口业务逻辑都堆在里面用 QML 意味着推倒重来。而 QSS 的侵入性小得多加载一个样式表全局控件外观统一变掉业务代码一行不用动。这也是这个QT通用框架资源包会选择 QSS 而不是 QML 的根本原因它要做的是给 C 软件换皮不是换骨架。2.3 把样式文件接入工程的三步走接入动作很简单读文件、拼字符串、设到全局。常见做法是写一个工具函数启动时调用一次#include QApplication #include QFile #include QTextStream static QString loadQssFile(const QString path) { QFile f(path); if (!f.open(QFile::ReadOnly | QFile::Text)) { return QString(); } QTextStream in(f); QString content in.readAll(); f.close(); return content; } void applyQtStyle(const QString basePath, const QString themePath) { QString qss loadQssFile(basePath); if (!themePath.isEmpty()) { qss loadQssFile(themePath); } qApp-setStyleSheet(qss); }这个函数里basePath是基础样式表themePath是主题叠加层第二个参数可以为空。拼接顺序是有讲究的在 QSS 里后声明的同名选择器会覆盖先声明的所以主题文件一定放在 base 后面才能覆盖默认颜色。int main(int argc, char *argv[]) { QApplication app(argc, argv); applyQtStyle(:/styles/default.qss, :/styles/light.qss); // 正常的窗口创建、show()、exec() }这里用的是:/资源路径也就是图片和 QSS 都打进 qrc 的常规做法。注意QTextStream默认按本地编码读如果 QSS 文件里有中文注释或中文字体名建议统一保存成 UTF-8 编码否则在 Windows 上字体名可能变成乱码样式直接失效。提示别在单个窗口里再调setStyleSheet去覆盖全局样式。窗口级样式表优先级高于全局一旦写了后面换主题时那个窗口会纹丝不动排查起来很麻烦。3. QSS 核心写法全局字体、主窗口与高频控件的样式落点3.1 先管住全局字体、窗口底色与透传关系拿到框架后第一件要做的事是理解 default.qss 最前面的全局选择器写了什么。大多数包里开头都是这样一段QWidget { font-family: Microsoft YaHei, PingFang SC, sans-serif; font-size: 13px; color: #3c3c3c; } QMainWindow, QDialog { background: #f5f6f8; } QWidget#centralWidget { background: transparent; }第一段QWidget会作用到所有继承自 QWidget 的控件字体、字号、默认文字颜色统一从这里来。这里的13px是逻辑像素在 Windows 下对应正常大小的 13 号字不要用pt否则不同系统下字号差距很大。第二段给主窗口和对话框铺底色。这里有个容易踩的点QMainWindow的背景会透传到中央部件如果centralWidget不是透明就会出现一层灰色叠一层灰色的效果。所以第三段把中央部件设成透明让主窗口底色作为统一背景。3.2 高频控件按钮、输入框、下拉框的状态写法框架包里最有含金量的部分是高频控件的完整状态覆盖。默认样式通常长这样QPushButton { min-height: 28px; padding: 0 16px; background: #ffffff; border: 1px solid #d9dce1; border-radius: 4px; color: #333333; } QPushButton:hover { background: #f2f7ff; border-color: #7ab8ff; } QPushButton:pressed { background: #d7e6f7; } QPushButton:disabled { color: #a0a4ab; border-color: #e0e2e6; background: #f5f6f8; } QPushButton:checked { background: #e8f1fd; border-color: #4a90d9; } QLineEdit { padding: 4px 8px; border: 1px solid #d9dce1; border-radius: 4px; background: #ffffff; selection-background-color: #bcd9f5; } QLineEdit:focus { border-color: #4a90d9; } QComboBox { min-height: 26px; padding: 0 8px; border: 1px solid #d9dce1; border-radius: 4px; background: #ffffff; } QComboBox::drop-down { width: 22px; border: none; } QComboBox::down-arrow { image: url(:/resources/arrow_down.png); width: 10px; height: 6px; }这里几个参数值得说。按钮的min-height不建议低于 26Windows 下会显得扁padding: 0 16px让按钮宽度跟随文字走而不是写死宽度。:checked是为 checkable 按钮准备的如果业务里没有 toggle 按钮这一条可以忽略但不要删掉框架里的规则因为框架里可能有别的地方用了它。输入框的selection-background-color控制的是文本选中时的底色不写的话默认是深蓝在浅色主题下会显得很突兀。下拉框的::drop-down是右侧箭头的区域如果不加border: none这里会出现一条明显的竖线::down-arrow引用的图片路径必须在 qrc 里真实存在否则箭头会消失。你说这是小问题也行但实际项目里十有八九会碰到。3.3 右键菜单、ToolTip 与分隔条被忽略的细节决定观感很多包的 default.qss 里会在后半部分处理菜单和提示这一段常被忽略但视觉效果贡献很大QMenu { background: #ffffff; border: 1px solid #dcdfe4; border-radius: 6px; padding: 4px; } QMenu::item { padding: 6px 24px 6px 12px; border-radius: 4px; } QMenu::item:selected { background: #e8f1fd; } QMenu::separator { height: 1px; margin: 4px 8px; background: #e4e7eb; } QToolTip { color: #ffffff; background: #3c3c3c; border: none; padding: 4px 8px; }菜单的::item:selected和::item:hover在很多 Qt 版本里等价但写成 selected 更稳。分隔条的margin: 4px 8px控制的是左右留白和上下间距只调高度不调 margin会得到一条顶天立地的分隔线很丑。ToolTip 的border: none一定要写Windows 下不写会出一圈默认边框跟深色背景完全不搭。3.4 QSS 的优先级规则后写覆盖先写objectName 高于类看到这里你应该已经发现QSS 的核心是把 CSS 的选择器思路搬到了 Qt 控件上。实际调样式时经常会遇到“我写了为什么没生效”的困惑绝大多数是优先级问题。QWidget 类选择器 优先级最低 QPushButton 类选择器 次低 QPushButton:hover 伪类 其次 QWidget#myButton 最高在同一优先级下后声明的规则覆盖先声明的。这解释了为什么主题文件要放在基础样式后面也解释了为什么不要随便给某个控件单独setStyleSheet——那相当于给控件加了一个最高优先级的行内样式会直接盖掉全局主题。提示当你发现某个按钮颜色怎么改都变不了第一件事检查它有没有在业务代码里被单独设过样式而不是继续往全局样式表里加规则。4. 从通用框架到定制部件表格、滚动条与自定义控件的自定义套路4.1 表格和树形控件的视觉统一工具软件里最常见的重灾区是 QTableView 和 QTreeView默认灰底白线很难看。框架里通常会给表格铺一套底色和选中态QTableView { background: #ffffff; alternate-background-color: #fafbfc; gridline-color: #e8eaee; selection-background-color: #d7e6f7; selection-color: #1a1a1a; } QTableView::item { padding: 4px; } QTableView::item:hover { background: #f0f7fe; } QHeaderView::section { background: #f0f2f5; color: #3c3c3c; padding: 6px 8px; border: none; border-right: 1px solid #dcdfe4; border-bottom: 1px solid #dcdfe4; font-weight: 500; }alternate-background-color需要配合代码里的setAlternatingRowColors(true)才会生效它让表格行交替出现底色长列表的阅读体验会好很多。selection-color是选中行的文字颜色很多默认样式里没写选中后文字变成白色在浅蓝背景上对比度过高刺眼。表头的QHeaderView::section是难点。常见错误是只写background和border: none结果相邻表头之间没有任何分隔线看起来黏成一片。上面的写法是去掉默认边框后用右边框模拟竖分隔线视觉比较干净。如果你想要整块深色表头把background改成 #3c3c3c、color改成白色就行分隔线颜色也换深色系。表格部分还需要说一句QTableWidget 本身继承自 QTableView所以上述样式直接覆盖它不需要单独写 QTableWidget 选择器。树形控件写法类似只是把QTableView换成QTreeView即可。4.2 滚动条贴图替换前的尺寸与布局参数滚动条是另一个让人头疼的控件。默认滚动条粗且占空间框架里一般会压窄并给滑块加圆角QScrollBar:vertical { background: #f5f6f8; width: 10px; margin: 0; } QScrollBar::handle:vertical { background: #c6cad1; min-height: 24px; border-radius: 5px; } QScrollBar::handle:vertical:hover { background: #a9afb8; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; }width: 10px是个人偏好Windows 下原版滚动条约 17px压到 10px 后界面明显轻快但触屏设备上会不好抓建议触屏场景保持 14px 以上。add-line和sub-line是上下两个箭头按钮高度设 0 是为了让它们不再挤占滑块的可用空间——这是滚动条滑块“跳动”或“短一大截”的主要根源。add-page和sub-page是滑块上下的轨道区域设成透明后点击轨道不再跳页只靠拖拽滑块滚动。这个习惯看个人保留跳页功能的话就删掉这两行。横向滚动条同理把vertical换成horizontalwidth换成height。4.3 自定义控件用 qproperty 接样式而不是写死颜色框架里如果你要定义自己的业务卡片或状态指示块不建议在 C 代码里写死颜色。更通用的做法是给自定义控件声明一个 Qt 属性用 QSS 的qproperty-前缀去赋值class InfoCard : public QWidget { Q_OBJECT Q_PROPERTY(QColor cardColor READ cardColor WRITE setCardColor) public: explicit InfoCard(QWidget *parent nullptr); QColor cardColor() const { return m_cardColor; } void setCardColor(const QColor color) { m_cardColor color; update(); } private: QColor m_cardColor QColor(#4a90d9); };自定义控件内部在paintEvent里用m_cardColor去做填充和描边然后在 QSS 里这样写InfoCard { qproperty-cardColor: #4a90d9; background: transparent; } InfoCard[statuswarning] { qproperty-cardColor: #e8930c; }这样做的价值在于颜色完全由样式表控制不同主题下可以给 InfoCard 赋不同的卡片色业务代码一行不改。注意Q_PROPERTY宏和Q_OBJECT宏必须都在setCardColor里要调用update()触发重绘否则改了值画面不刷新。背景色要设透明因为卡片内容是控件自己画的QSS 再铺一层底会出现叠色。4.4 按部件拆文件维护时不在 2000 行 QSS 里翻找框架把默认样式按部件拆到 parts 目录这个思路很实用。我自己接项目时也习惯把 QSS 拆成多段独立文件styles/parts/buttons.qss styles/parts/inputs.qss styles/parts/menu.qss styles/parts/table.qss styles/parts/scrollbar.qss每个文件只维护一类控件多人协作时改按钮的人在 buttons.qss 里改改表格的人在 table.qss 里改不会互相覆盖。加载时按固定顺序拼成一个字符串再设置到全局顺序和前面提到的主题叠加逻辑一致基础片段在前面主题覆盖在后。框架包里的 parts 目录已经帮你做了这个拆分直接用就行。如果你拿到的包是单文件也可以按上面的思路自己拆后续改主题会轻松很多。5. 避坑排查QSS 不生效、圆角变方角、滚动条抽风的六个先例5.1 现象把样式表拷进新工程所有控件恢复默认原因最常见的是setStyleSheet传了一个空字符串。空字符串在 Qt 里的语义是“清除样式”而不是“不设置”。常见坑包括qrc 路径写错、文件名大小写不对、文件没被加入 qrc。解决在读取 QSS 之后立刻打印长度先确认内容真的读到了QString qss loadQssFile(:/styles/default.qss); qDebug() QSS length: qss.size(); app.setStyleSheet(qss);如果长度为 0去检查 qrc 里的路径前缀是不是少了冒号或者文件后缀名是不是.qss而不是.txt。这一步排查几十秒能省掉后面所有的“为什么没生效”的猜测。5.2 现象同一份 QSS升级 Quot 版本后圆角变成方角原因QSS 是字符串解析不同版本的解析器对盒模型和伪类的容错处理不完全一致。最常见的是border-radius写在了没有background的控件上某些版本解析器会忽略圆角另一类情况是QPushButton在特定 style 下自带绘制逻辑QSS 覆盖不彻底。解决不要试图兼容所有 Qt 版本按包注释的版本维护一份样式主文件。框架包升级时先跑一遍 demo 工程按钮圆角、菜单圆角、输入框聚焦这三处最容易看出差异。如果业务方版本跨度很大考虑用贴图替代圆角border-image的兼容性比border-radius稳得多。5.3 现象按钮 hover 背景不生效hover 和普通态分不出来原因常见的坑有两个。一是QPushButton:hover写在了QPushButton前面同级优先级下后面的覆盖前面的hover 背景被普通态背景盖掉。二是按钮上设置了border-image背景色被贴图整个盖住无论怎么调 background 都看不见。解决把普通态、hover、pressed 按顺序依次排列保证状态伪类出现在普通态之后。如果按钮要贴图就不要同时给普通态写背景色贴图本身就负责显示。检查顺序时先看 QSS 文件里选择器的前后位置这个自检比翻代码快。5.4 现象高分屏下界面和贴图发糊边框粗细不均原因Qt 5 默认不开启高 DPI 缩放否则界面按逻辑像素布局在 150% 缩放 Windows 上要么小得费眼要么发虚。贴图模糊大概率是资源里只放了一倍图缩放后靠算法拉伸。解决在 main 函数最开始、创建 QApplication 之前加上 DPI 属性。Qt 5 的工程里常见做法是在 main 第一行写QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);Qt 6 默认开启不需要这行代码。贴图部分给图标资源的icons.png备 2 倍尺寸并在 QSS 里用width和height锁显示尺寸不要让它按原始像素撑开。5.5 现象滚动条滑块长度异常在顶部或底部“跳”原因add-line和sub-line默认有箭头的区域尺寸如果不显式给高度 0滑块和箭头会在同一块固定空间里相互抢占内容一旦变化滑块位置就跳。解决在滚动条样式里显式写出QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; } QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }同时给滑块设min-height或min-width兜底。之后拖动、滚轮、键盘翻页都会走同一个滑块逻辑不会再出现“一滚就跳到头”的体验。5.6 现象动态属性 qproperty 赋值不生效原因自定义控件没有Q_OBJECT宏或者Q_PROPERTY的读写函数声明和实现不一致。QSS 里的qproperty-是通过元对象系统赋值的没有元对象就没有赋值入口。解决确认类头文件第一行是Q_OBJECTQ_PROPERTY放在类体内且READ和WRITE指定的函数名与实现在类中完全一致。注意qproperty-cardColor在 QSS 里是去掉 Q_PROPERTY 第一参数中的QColor类型、把首字母小写得到的多一个大写字母都匹配不上。6. 拿到资源后先做这一步一张“样件窗口”测完所有状态把框架接进业务项目之前建议先花半小时做一个专用验证窗口把包里的控件和状态一次性铺上去int main(int argc, char *argv[]) { QApplication app(argc, argv); applyQtStyle(:/styles/default.qss, :/styles/light.qss); QMainWindow w; // 每组控件都放一组normal、hover、pressed、disabled、checked // 输入框覆盖 normal、focus、disabled // 表格放几百行测交替背景和选中行 // 滚动条内容超长测拖拽、滚轮、跳页 // 右键菜单弹出的样式也在这里看 w.show(); return app.exec(); }这样做的原因是QSS 的问题通常不在“写没写”而在“状态对不对”。hover 背景、输入框聚焦边框、菜单选中行、表头分隔线、滚动条滑块这些状态只有专门铺出来才看得到。我一般每次拿到主题包都会做一轮差分检查记在验证清单里检查项 期望效果 容易出错的点 按钮 hover 边框变浅蓝、背景略亮 被 border-image 盖住 输入框 focus 边框变主色 忘记写背景色变化 表格选中行 背景浅蓝、文字颜色不变深 没写 selection-color 菜单 selected 选中项有圆角背景 菜单项 padding 太窄文字贴边 滚动条拖拽 滑块圆角无锯齿 add-line 没归零滑块抖动换主题只改 QSS 文件不动任何代码然后跑一遍样件窗口状态和参数都对得上再进业务页面。从那以后我每次接到新的框架包都强制先建一个样件窗口把按钮、表格、滚动条、右键菜单先堆一遍再考虑业务接入因为多数界面问题在样件窗口上都会现形等进了业务再排查就慢了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?