新贡献者开发指南¶
- 完整开发路径: 从零到高效开发的完整引导,涵盖环境搭建、架构理解、编码实践
- 架构概览: 通过 Mermaid 图表快速理解项目层次结构和模块关系
- 设计模式详解: PIMPL、工厂模式、策略模式、单例模式的实际用法与代码示例
- 开发工作流: 添加按钮类型、新主题、新控件等典型场景的分步流程图
- 代码模板: 可直接复制使用的 .h/.cpp 新类模板
- 禁止事项速查: 明确列出不可修改的文件和必须遵守的编码规则
- 调试与提交: 调试技巧、Git 工作流、commit message 格式规范
项目概述¶
SARibbon 是一个基于 Qt 框架的 Ribbon 界面控件库,为 C++ 桌面应用提供类似 Microsoft Office 的功能区(Ribbon)界面。项目版本号 2.8.0,采用 MIT 开源协议,支持 Qt 5.12 及以上版本(包括 Qt 5 和 Qt 6),最低要求 C++14 标准。
该项目解决的核心问题是:传统 Qt 应用的菜单栏(QMenuBar)和工具栏(QToolBar)是平级结构,无法实现 Office 风格的层级式 Ribbon 界面——即"标签页 → 面板 → 按钮"的三级组织结构。SARibbon 通过继承 QMenuBar 的 SARibbonBar 实现了这一层级关系,同时保持了与 Qt 现有 QAction/QMenu 体系的完全兼容。
技术栈选型理由:选择 Qt 作为基础框架是因为其跨平台能力和成熟的 Widget 体系;使用 PIMPL 模式确保 ABI 兼容性和编译隔离;工厂模式(SARibbonElementFactory)允许用户替换任意子组件的实现;CMake 构建系统同时支持 Visual Studio 和 Ninja 生成器。
快速开发环境搭建¶
环境要求¶
| 组件 | Windows | Linux / WSL |
|---|---|---|
| 编译器 | Visual Studio 2019 (MSVC 14.29+) | GCC 9+(推荐 GCC 13+) |
| CMake | 3.15+ | 3.15+ |
| Qt 版本 | Qt 6.7+ 或 Qt 5.12+ | Qt 6.x(apt)或 Qt 5.12+ |
| C++ 标准 | C++14(Qt5)/ C++17(Qt6) | 自动根据 Qt 版本选择 |
| 构建工具 | Visual Studio 或 Ninja | Ninja |
构建命令¶
Windows(Visual Studio 生成器,推荐):
1 2 3 4 5 | |
Windows(Ninja 生成器):
1 2 3 4 5 6 7 8 9 10 | |
Linux / WSL:
1 2 3 4 5 6 | |
运行测试¶
1 2 3 4 5 6 | |
运行示例程序¶
编译完成后,示例程序位于 build/bin/ 目录下,最主要的是 MainWindowExample:
1 2 3 4 5 | |
项目目录结构¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | |
各目录职责¶
| 目录 | 职责 | 编辑频率 |
|---|---|---|
src/SARibbonBar/ |
所有核心源码,包含全部 .h 和 .cpp 文件 | 高频 |
src/SARibbonBar/colorWidgets/ |
颜色选择器子模块 | 低频 |
src/SARibbonBar/3rdparty/ |
第三方代码,通常不需要修改 | 极低 |
example/ |
示例程序,用于验证和演示功能 | 中频 |
tests/ |
单元测试,需 BUILD_TESTS=ON 启用 |
中频 |
tools/ |
Amalgamate 合并工具,用于生成单文件版本 | 极低 |
docs/ |
文档目录 | 低频 |
禁止修改的文件和目录¶
严格禁止修改的文件
以下文件和目录绝对禁止修改,任何改动都将导致构建问题或与合并工具冲突:
src/SARibbon.cpp和src/SARibbon.h:这两个文件由tools/下的 Amalgamate 工具自动生成,将src/SARibbonBar/下的所有源文件合并为单文件。手动修改会在下次生成时被覆盖。src/SARibbonBar/SARibbonBarVersionInfo.h:由 CMake 的configure_file从.h.in模板自动生成,记录版本号信息。
所有源码改动必须在 src/SARibbonBar/ 目录下进行。
核心架构概览¶
SARibbon 的架构遵循 Qt Widget 的层级组合模式,从顶层主窗口到底层按钮形成清晰的树状结构。
flowchart TB
subgraph "主窗口层"
MW[SARibbonMainWindow<br/>QMainWindow]
RW[SARibbonWidget<br/>QWidget]
end
subgraph "Ribbon 栏层"
RB[SARibbonBar<br/>QMenuBar]
TB[SARibbonTabBar<br/>QTabBar]
QAB[SARibbonQuickAccessBar]
SBB[SARibbonSystemButtonBar]
end
subgraph "分类页层"
CC[SARibbonContextCategory<br/>上下文标签]
CAT[SARibbonCategory<br/>QFrame]
end
subgraph "面板层"
PNL[SARibbonPanel<br/>QFrame]
GAL[SARibbonGallery]
end
subgraph "按钮层"
BTN[SARibbonToolButton<br/>QToolButton]
end
MW --> RB
RW --> RB
RB --> TB
RB --> QAB
RB --> SBB
RB --> CC
RB --> CAT
CC --> CAT
CAT --> PNL
PNL --> BTN
PNL --> GAL
整个架构分为五个层次,每个层次的类只与相邻层次直接交互:
-
主窗口层:
SARibbonMainWindow继承QMainWindow,负责将SARibbonBar替换原有的QMenuBar,管理窗口边框和标题栏。SARibbonWidget是 QWidget 版本,用于不需要 QMainWindow 的场景。 -
Ribbon 栏层:
SARibbonBar继承QMenuBar,是 Ribbon 的核心管理类。它组合了SARibbonTabBar(标签栏)、SARibbonQuickAccessBar(快速访问栏)和SARibbonSystemButtonBar(系统按钮栏),并通过SARibbonBarLayout管理整体布局。 -
分类页层:
SARibbonCategory对应一个 Tab 页面,SARibbonContextCategory管理上下文相关的标签组(如 Office 中选中表格时出现的"表格工具"标签)。 -
面板层:
SARibbonPanel是 Category 内的功能分组容器,通过SARibbonPanelLayout管理内部按钮的排列。SARibbonGallery是一种特殊的面板内容,提供下拉选择列表。 -
按钮层:
SARibbonToolButton是最终的用户交互控件,管理QAction,支持大按钮和小按钮两种显示模式。
模块关系图¶
classDiagram
class SARibbonMainWindow {
+ribbonBar() SARibbonBar*
+ribbonStyle() SARibbonTheme
}
class SARibbonBar {
+addCategoryPage() SARibbonCategory*
+addContextCategory() SARibbonContextCategory*
+setRibbonStyle()
+applicationButton() QAbstractButton*
}
class SARibbonCategory {
+addPanel() SARibbonPanel*
+categoryName() QString
+panelList() QList
}
class SARibbonPanel {
+addLargeAction() SARibbonToolButton*
+addSmallAction() SARibbonToolButton*
+addGallery() SARibbonGallery*
+setOptionAction()
}
class SARibbonToolButton {
+buttonType() RibbonButtonType
+setButtonType()
}
class SARibbonElementFactory {
<<virtual>>
+createRibbonBar()
+createRibbonCategory()
+createRibbonPanel()
+createRibbonToolButton()
}
class SARibbonElementManager {
+instance()$ SARibbonElementManager*
+factory() SARibbonElementFactory*
+setupFactory()
}
class SARibbonBarLayout {
+doLayout()
+resetSize()
}
class SARibbonCategoryLayout {
+addPanel()
+doLayout()
+scroll()
}
class SARibbonPanelLayout {
+insertAction()
+doLayout()
}
SARibbonMainWindow --> SARibbonBar : 拥有
SARibbonBar --> SARibbonCategory : 管理
SARibbonBar --> SARibbonBarLayout : 使用
SARibbonCategory --> SARibbonPanel : 包含
SARibbonCategory --> SARibbonCategoryLayout : 使用
SARibbonPanel --> SARibbonToolButton : 包含
SARibbonPanel --> SARibbonPanelLayout : 使用
SARibbonElementManager --> SARibbonElementFactory : 持有
SARibbonElementFactory ..> SARibbonBar : 创建
SARibbonElementFactory ..> SARibbonCategory : 创建
SARibbonElementFactory ..> SARibbonPanel : 创建
SARibbonElementFactory ..> SARibbonToolButton : 创建
依赖方向说明:依赖关系严格自上而下——主窗口拥有 Ribbon 栏,Ribbon 栏管理分类页,分类页包含面板,面板包含按钮。布局类与对应的控件类一一配对。工厂类通过单例管理器全局可访问,用于创建所有子组件。下层模块不依赖上层模块,这保证了各层可以独立测试和替换。
新功能开发工作流¶
场景一:添加新的 Ribbon 按钮类型¶
当需要在现有面板中添加一种新的按钮样式(例如带颜色选择的按钮)时:
flowchart TD
A[分析需求:确定新按钮的视觉和交互行为] --> B[继承 SARibbonToolButton]
B --> C[重写 paintEvent 绘制逻辑]
C --> D[在 SARibbonElementFactory 中添加创建方法]
D --> E[在 SARibbonPanel 中添加便捷添加方法]
E --> F[更新 example/MainWindowExample 验证效果]
F --> G[编写单元测试]
G --> H[提交代码]
关键步骤说明:
-
继承 SARibbonToolButton:在
src/SARibbonBar/下新建SARibbonMyButton.h和.cpp,继承SARibbonToolButton,重写paintEvent、sizeHint等方法。参考现有的SARibbonColorToolButton实现。 -
工厂注册:在
SARibbonElementFactory中添加虚方法virtual SARibbonMyButton* createRibbonMyButton(QWidget* parent),默认实现返回标准实例。 -
面板集成:在
SARibbonPanel中添加addMyButton()便捷方法,内部通过RibbonSubElementFactory创建实例。
场景二:添加新主题¶
当需要添加一个新的 Ribbon 主题(如深色 Office 2024 风格)时:
flowchart TD
A[在 SARibbonTheme 枚举中添加新主题值] --> B[编写对应的 QSS 样式表]
B --> C[在 SARibbonBar 的 setRibbonTheme 中添加分支]
C --> D[调整 SARibbonTabBar 的 margin 参数]
D --> E[验证 ContextCategory 绘制效果]
E --> F[在 example 中切换主题测试]
F --> G[确保所有风格下布局正确]
关键注意事项:
- 在
SARibbonGlobal.h的SARibbonTheme枚举中添加新值(如RibbonThemeOffice2024Dark) - 主题的 QSS 尺寸信息无法在 C++ 代码中自动获取,需要手动设置 margin 参数到
SARibbonTabBar - 测试时需要覆盖所有
RibbonStyles(Loose/Compact x ThreeRow/TwoRow/SingleRow)
场景三:添加新控件¶
当需要向 Ribbon 系统添加全新类型的控件(如日期选择器、滑块等)时:
flowchart TD
A[设计控件的 Ribbon 集成方式] --> B{作为面板内容还是独立控件?}
B -->|面板内容| C[创建 SARibbonXxxWidget 类]
B -->|独立控件| D[创建继承 QFrame 的容器类]
C --> E[实现 SARibbonCtrlContainer 包装]
D --> F[注册到 SARibbonElementFactory]
E --> F
F --> G[在 SARibbonPanel 中添加便捷方法]
G --> H[添加到 CMakeLists.txt 的源文件列表]
H --> I[编写示例和测试]
关键设计原则:
- 如果新控件是嵌入面板内的(如 ComboBox),继承
SARibbonCtrlContainer包装 - 如果新控件是独立容器(如新的 Gallery 变体),继承
QFrame并使用 PIMPL 模式 - 务必在
SARibbonElementFactory中添加对应的虚创建方法,保持工厂模式的完整性 - 新文件必须加入
src/SARibbonBar/CMakeLists.txt的SARIBBON_HEADERS和SARIBBON_SOURCES列表
设计模式与约定¶
核心设计模式¶
| 模式 | 应用类 | 目的 | 代码位置 |
|---|---|---|---|
| PIMPL | 所有核心类 | 隐藏实现细节,保持 ABI 稳定 | SARibbonGlobal.h 宏定义 |
| 工厂方法 | SARibbonElementFactory |
允许替换任意子组件实现 | 17 个虚创建方法 |
| 策略模式 | SARibbonButtonLayoutStrategy |
大/小按钮不同布局算法 | 抽象基类 + 两个具体策略 |
| 单例 | SARibbonElementManager |
全局访问工厂实例 | instance() 静态方法 |
PIMPL 模式详解¶
项目使用自定义宏实现 PIMPL,基于 std::unique_ptr(非 Qt 的 QScopedPointer)。完整用法参见 PIMPL 开发规范。
头文件中——紧跟 Q_OBJECT 之后声明:
1 2 3 4 5 6 7 8 9 10 | |
源文件中——定义 PrivateData 内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
工厂模式详解¶
SARibbonElementFactory 提供 17 个虚方法,覆盖所有子组件的创建。通过 SARibbonElementManager 单例全局访问:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
工厂内部的便捷宏:
1 2 3 | |
策略模式详解¶
SARibbonButtonLayoutStrategy 定义了按钮布局计算的接口,大按钮和小按钮使用不同的策略实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
命名约定速查表¶
| 类别 | 规范 | 示例 |
|---|---|---|
| 类名 | SARibbon 前缀 + 大驼峰 |
SARibbonBar, SARibbonCategory |
| 方法名 | 小驼峰,Qt 风格 | setRibbonStyle(), addCategoryPage() |
| 属性名 | 小驼峰,Qt 风格 | ribbonStyle, categoryName, panelName |
| 信号 | xxxChanged 模式 |
ribbonStyleChanged(), categoryNameChanged() |
| 私有数据类 | PrivateData |
定义在 .cpp 中的内部类 |
| 枚举 | 大驼峰,值用大驼峰 | RibbonStyleFlag::RibbonStyleLooseThreeRow |
| 宏 | SA_RIBBON_ 前缀 + 大写 |
SA_RIBBON_EXPORT, SA_RIBBON_DECLARE_PRIVATE |
| 私有成员变量 | m 前缀 + 大驼峰 |
mRibbonStyle, mCurrentRibbonMode |
| 文件命名 | 与主类名一致 | SARibbonBar.h, SARibbonBar.cpp |
新类/文件模板¶
头文件模板 (.h)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | |
源文件模板 (.cpp)¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
模板使用注意事项
- 新建的
.h和.cpp文件必须加入src/SARibbonBar/CMakeLists.txt的源文件列表 - 文件换行格式必须为 CRLF,LF 会导致
SARibbonAlignment枚举编译错误 .h中 public 函数只用单行英文///注释,双语 Doxygen 写在.cpp中
调试指南¶
常用调试手段¶
1. 启用调试打印宏
SARibbonBar.cpp 内置了调试绘制辅助宏,可以显示布局计算的矩形区域:
1 2 3 4 | |
2. 布局调试
当布局出现问题时,可以在 SARibbonBarLayout::doLayout()、SARibbonCategoryLayout::doLayout() 或 SARibbonPanelLayout::doLayout() 中设置断点,检查各组件的 geometry 计算值。
3. 样式表调试
使用 Qt 内置的 QSS 调试方法:
1 2 3 | |
4. 运行时检查
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
常见编译错误¶
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| "成员声明的限定名称非法" | 文件换行为 LF | 将文件换行改为 CRLF |
PrivateData 不完整类型 |
析构函数在 .h 中定义 | 将析构函数移到 .cpp 中定义 |
未定义的 SA_RIBBON_EXPORT |
缺少头文件引用 | 确保 #include "SARibbonGlobal.h" |
emit/signals/slots 编译错误 |
使用了禁止的关键字 | 改用 Q_EMIT/Q_SIGNALS/Q_SLOTS |
代码提交规范¶
Git 工作流¶
- 在
main分支上保持最新:git pull origin main - 创建功能分支:
git checkout -b feature/my-new-feature - 开发、测试、提交到功能分支
- 创建 Pull Request 合并回
main
Commit Message 格式¶
采用中文描述,包含任务类型、内容摘要、相关文件和关联信息:
1 2 3 4 5 6 | |
类型关键字:修复(Bug 修复)、实现(新功能)、优化(性能/代码质量)、文档(文档更新)、重构(代码重构)
示例:
1 2 3 4 5 6 | |
开发者 FAQ¶
Q1: 如何替换 Ribbon 中某个子控件的实现?¶
通过继承 SARibbonElementFactory 并重写对应的虚方法来实现。在 main() 函数中、创建任何 Ribbon 窗口之前,调用 SARibbonElementManager::instance()->setupFactory(new MyFactory) 设置自定义工厂。这样所有通过工厂创建的子组件都会使用你的自定义实现。
Q2: 为什么不能直接修改 src/SARibbon.h 和 src/SARibbon.cpp?¶
这两个文件由 tools/ 目录下的 Amalgamate 工具自动从 src/SARibbonBar/ 下的源文件合并生成。任何手动修改会在下次执行合并工具时被覆盖。所有改动必须在 src/SARibbonBar/ 目录下的原始源文件中进行。
Q3: PIMPL 模式下,为什么析构函数必须在 .cpp 中定义?¶
因为 SA_RIBBON_DECLARE_PRIVATE 使用 std::unique_ptr<PrivateData> 管理私有数据。unique_ptr 的析构需要看到 PrivateData 的完整定义,而 PrivateData 的完整定义仅在 .cpp 文件中可见。如果析构函数在头文件中定义(编译器生成默认实现),会导致"不完整类型"编译错误。
Q4: 如何让新增的文件参与编译?¶
在 src/SARibbonBar/CMakeLists.txt 中将新的 .h 文件加入 SARIBBON_HEADERS 列表,.cpp 文件加入 SARIBBON_SOURCES 列表。然后重新执行 CMake 配置命令。
Q5: Ribbon 的风格切换后布局不正确怎么办?¶
调用 SARibbonBar::updateRibbonGeometry() 强制重新计算布局。如果问题仍然存在,检查 SARibbonBarLayout::doLayout() 中对应风格的分支(resizeInLooseStyle() 或 resizeInCompactStyle())。另外确认 SARibbonTabBar 的 margin 信息是否与 QSS 样式一致。
Q6: SARibbonAlignment 枚举编译报错"成员声明的限定名称非法"?¶
这是文件换行格式问题。SARibbonGlobal.h 中 SARibbonAlignment 枚举的定义要求文件使用 CRLF 换行。如果你的编辑器保存为 LF 换行,编译器会报此错误。将文件换行格式改为 CRLF 即可解决。
Q7: 如何在不使用 SARibbonMainWindow 的情况下使用 Ribbon?¶
可以使用 SARibbonWidget(继承自 QWidget)代替 SARibbonMainWindow。SARibbonWidget 提供相同的 Ribbon 功能,但不包含 QMainWindow 特有的菜单栏替换逻辑。
延伸阅读¶
| 文档 | 内容 |
|---|---|
| 编码规范 | 命名规范、Doxygen 注释、Git 提交格式 |
| PIMPL 开发规范 | PIMPL 宏完整用法和代码示例 |
| Qt 集成规范 | Q_PROPERTY、信号槽、Qt 宏使用规范 |
| 构建指引 | CMake 构建选项详解 |
| 架构设计 | 项目整体架构分析和设计决策 |
| 模块详解 | 各模块的业务逻辑和 API 参考 |
| 贡献指南 | 贡献流程和协作规范 |