QT 经验积累
要积累经典 GUI 的概念 (如工具栏菜单栏的区别, 工具栏按钮的使用方式)
这样才能复用这些设计, 并且界面库本身也是按照这种思路来的.
这些设计都是考虑过人机交互的, 可能本身是从过去人们的使用习惯而来的. 没有特殊需求不需要特意去思考.
开发上, 如果有一种模式不熟悉, 可以先做其中一个打个样. 然后再进行模仿
- 信号槽的使用规范:
- 耦合和内聚
- 耦合时, 指针可以跨层级传递. 而调用只能用接口一级级向下. 这样如果是头文件的层级关系, 在耦合源文件不需要导入新的头文件 (跨层级的那个类)
- 如果多个对象共用指针要如何设计依赖? 什么时候创建和释放
- 耦合和内聚
零散
- 硬编码的用法:
- 一些在软件生命周期中确定的现实世界规范,可以不用程序去表示(因为还要计算,直接使用硬编码提前算好就节省时间)
- 严格的窗口控制信号,区分窗口操作的信号,才能在嵌入式平台解决,
- 键盘控制:手动定义 Action 绑定快捷键,架构设计上,可以用一个类统一按键类型,然后具体类再通过和这个统一类型做映射绑定
- 各个模块配置自己按键绑定的枚举,本身其实也就用数组做映射就好了,速度很快,可读性很强
- 枚举结束标识,Num,代表了枚举数量也可以进行遍历
- 事件系统的规范,继承实现事件或使用事件过滤,但是要抓清楚事件传递的流程(控制好事件的漏流)
- 事件过滤器原理和流程
- Event 函数传递事件
- static 类成员需要 cpp 中初始化
- Public 指针:如果某个部件和另一个模块是强依赖,其实不用过多的 GetSet,直接使用指针即可
- QApplication::instance() 的强制转换,这个实例返回的是 Core
- 各种几何,距离 Rect 的理解,其实简单理解即可,就是一些图形定义,控制显示的。
- 宏定义的层次性
- 方法重载的设计:如索引一个列表数据,底层使用数组进行索引分配,重载使用字符串转数组
- Qt::BlankCursor 进行光标隐藏
- Focus 焦点:setTabOrder 规定顺序,setFocusProxy 代理焦点
- 部件结构:对于整理事件、焦点顺序等有帮助;dumpObjectTree 打印对象树,QApplication::focusWidget(); 追踪焦点链
- TableView 的高性能使用和界面显示操作的统一
- sendEvent 可以直接发送事件
- QApplication 或者 QWidget 的一些全局函数可能对简化一些逻辑有帮助
- nullptr 很多时候不是马上出现的,而是项目修改中出现的
- 编码:使用字符串常量时要考虑到代码文件的编码符号,可以用 QString 统一编码,不然可能会导致正则之类的地方出错
- 表格,TableView 的性能更高
- 如何实现 model :实现 Model 积累的相关方法,MVC 之间是相互通过特定接口连接的,还有个委托。
- model 还有一个代理 model 是在数据层的划分,如排序等
- model 代理
- 复杂几何采用场景设计
- 键盘事件产生需要添加 text
- 异构设备如何统一键盘事件
- qss 和属性结合可以更容易自定义外观,现有代码后修改 qss,所以一开始要有预留
- bind 可以绑定对象,使用类方法
- c++ 的浮点计算似乎有些不太熟悉了
- 菜单的弹出和菜单构建形式统一(可拓展),绑定快捷键
- QT 翻译,tr 和 QT_TRANSLATE_NOOP 都需要 Q_OBJECT 的支持(vs 中需要手动配置头文件才会自动生成 moc)
- 基础的 2D 绘制技巧
- 设计出 Qss 的工程技巧,如何设计布局
- 得熟悉一下 std 标准库以及 c++11 的内容(个人提升)
- std::accumulate:求和
- std::transform:映射
- 与 std::back_inserter 结合使用
- std::move:移动语义
- 关于 ObjectName:如果这个名称用于 qss,那么其他地方用到就要小心,范围涵盖的问题
- 在配置相关的地方一定要设计数据层和界面层,这样在配置的保存,载入,和默认配置上能做得出来。打但是要考虑配置熟悉的异构数据结构泛化
- 配置数据要单独分块,用户数据也一样
- 数据层不分块会杂糅
- C++ 程序员其实意味着,更全面的问题思考,更多复杂的问题需要解决
- 一些数据类的运算符重载很重要:可以简化一些判断操作的实现
- QT 并发:QtConcurrent
- 对于嵌入设备的按键等输入(甚至是更抽象的输入方式),直接统一转换映射成 PC 上 QT 的按键事件即可
- 要把握界面元素的复用和一般类面向对象的异同,界面元素要考虑不同需求下的兼容性,比较精细(因为要对接设计)
- 对话框的组合设计非常好,易于使用
- 可以多用模板进行泛型编程,统一操作逻辑
- 多线程:
- 一些对象,IO、定时器等,不能跨线程调用,要在对应的线程中 new 出。
- QT 中简单做就是把工作对象放到工作线程, 然后启动线程, 通过信号交互
- 表格和委托的妙用
- 委托:(视图会提供一个 editor Widget 容纳显示部件)
- createEditor 弹出编辑框
- setEditorData 设置编辑数据
- setModelData 保存编辑数据到模型
- (可选)updateEditorGeometry 更变编辑器大小
- 委托对象只有一个,所以只能重绘控件
- 所以 TableView 使用得更广泛,因为不需要组合 Widget
- 委托:(视图会提供一个 editor Widget 容纳显示部件)
- 半模态窗口:冻结用户交互,但是 show 后可以继续运行
setModal(true);/setWindowModality()QApplication::processEvents()执行过程需要使用这条才能后台执行并且和对话框交互
- 开发过程中多关注公共控件,保证公共控件的泛化能力随时调整,就像 b 树一样
- QSplashScreen 的设计很重要,影响大型软件的初始化和加载
- 可以直接使用
Qt::SplashScreen窗口标志 - 主要是这个界面不可以被关闭
- 可以直接使用
- 多态析构:用到多态时,析构设置为虚函数,能正确清理在基类指针中的派生类对象
- 私有布局:在主 Widget 上已经有一个布局,那么就使用私有布局(创建 Layout 父 Widget 设置为主 Widget),这样能实现布局的叠加
- 翻译:每次翻译要全部翻译完,然后去除 unfinished。注意要有 Q_OBJECT 才能翻译,并且需要给类带上命名空间(宏定义不会带命名空间)
- 灵活应用伪状态可以简化界面流程
- 与其手动布局,不如多用 ui,再把动态元素的指针赋值给对象
- 很多错误可能是缺少头文件或者前置声明
- VS 链接要到最终项目那边设置 QT 模块
- 对于频繁改动 (附近的类), 使用前置声明
- 项目设计时是能明确哪些类继承至什么地方的
项目工程
- 项目工程上, 综合眼光和决策很重要;能够透视整个架构,并且需要思维敏捷
- 各个模块的依赖关系要在设计阶段确定,不能随意更改,要慎重. 这样一开始的依赖关系设计好就能更快创建项目
- MVC 的解耦效果很好
- 不同的项目要分清楚内外层级关系
- 控制器就要分内和外
- 每个视图尽可能的独立于其他视图,不要耦合在一起
- 先定义项目公共头, 然后需要创建的类先只定义头文件
- 这样先划分好模块层级关系, 可以预先创建好指针, 然后后续就不用再处理这个逻辑了.
- 指针创建从最外层 Data 和 Controller 开始创建
- 项目级的主要类除了管理项目内其他类之外, 还负责与其他项目之间的联系
- 定义发送和接收枚举, Man 处只连接项目类的发送和接收, 至于内容怎么使用则是靠内部进行实现
- 这样就能保证模块之间的独立性
- 项目级的主要类除了管理项目内其他类之外, 还负责与其他项目之间的联系
- 提议?: View 尽量去除层级关系, 用并列关系避免界面的嵌套.
- 但是容易混杂所有窗口在 View 中, 目前先使用标识做分隔
- 单例界面?
- 直观简单的逻辑直接连接
- 要处理好固定在内存的东西和动态创建的东西
- 每个独立的类/文件/模块就包含两样: 定义和逻辑. (有过程式编程的思想)
- invoke 异步调用或者定时器
- 事件循环处理非堵塞同步流程
- 全局的事件(信号)单例类 AppEvent
- cpp 文件中使用宏 Q_OBJECT 这种情况下需要在文件结尾添加如下:
#include "classname.moc"
对于 moc 的类, 需要一开始设计清楚. 明确哪些使用 moc 信号槽. 哪些是非 moc 类的.
多继承
私有继承继承实现, 公有继承继承接口.
菱形继承:
- 如果多继承有相同的函数, 则会保存两份
- 需要加
virtual标记为虚继承, 但是虚继承消耗大.
- 需要加
最佳实践是继承纯虚类, 没有数据存在
多继承指针转换:
- 因为强制转换只是赋值, 所以得到的指针地址只和第一个继承类相同, 后续的类不会被解释. 因为丢失了继承信息, 所以获取指针时要先动态转换为对应接口类, 这样获取的指针才是正确的接口类指针位置.
- 简单说就是一个派生类, 多继承了多个接口. 如果这个派生类的指针直接强制转换为某个接口是无效的, 只有当这个接口是第一个继承的类才有意义. 本质是指针指向内存地址不同.
- 当派生类动态转换为某个接口是, 这时指针地址是对应接口的地址 (即使是后续继承的类/接口), 这样使用 void 强制转换回来才能有效.
问题:
引入其他模块时, 如何解决依赖问题?
- 如果是引入其他模块的最外层, 那么最外层需要提供很多接口.
- 如果是直接用其他模块内部对象, 则会造成过多的耦合
GPT 回答:
你提到的依赖问题在软件开发中是一个常见的挑战。要解决这个问题,可以采用以下几种方法:
- 依赖注入(Dependency Injection):通过依赖注入,你可以将一个对象的依赖关系从其内部移动到外部,从而减少模块之间的耦合。这通常通过构造函数注入、方法注入或属性注入来实现。这种方式有助于使代码更加灵活和可测试。(属性注入就是直接指针赋值)
- 面向接口编程:面向接口编程是一种设计模式,它将关注点从具体的实现转移到接口。通过定义接口,你可以为模块之间的交互定义一组规范,而不是直接依赖于具体的实现。这样做有助于降低耦合度,增加代码的灵活性和可维护性。
- 依赖倒置原则(Dependency Inversion Principle):依赖倒置原则要求高层模块不应该依赖于底层模块,二者都应该依赖于抽象。换句话说,模块之间的依赖关系应该通过抽象来实现,而不是通过具体的实现类。这可以通过面向接口编程来实现。
- 模块化设计:将系统分解成小的、相互独立的模块,每个模块都有清晰的责任和接口。这样做有助于降低模块之间的耦合度,并且使得修改和扩展变得更加容易。
- 依赖查找(Dependency Lookup):这是一种反模式,通常被认为是不推荐的做法。在依赖查找中,一个对象通过查询容器或者其他管理器来获取所需的依赖。虽然这种方法可以减少模块之间的耦合,但它也增加了代码的复杂性,并且可能导致运行时错误。
在实际开发中,通常会结合使用上述方法来解决依赖问题。这些方法可以帮助你编写更具可维护性、可测试性和可扩展性的代码。`
焦点控制
dumpObjectTree()函数可用于打印当前窗口小部件及其子部件的树形结构,包括焦点顺序。这对于调试时了解窗口小部件的嵌套和层次结构很有帮助。dumpObjectInfo()函数可用于打印关于窗口小部件的信息,包括类名、对象名、父对象等。focusProxy()函数返回一个指向窗口小部件焦点代理的指针。通过递归使用该函数,你可以追踪窗口小部件的焦点链。 ```
|
|
-
nextInFocusChain()和previousInFocusChain(),这两个函数用于获取焦点链中的下一个或前一个窗口小部件。 -
还有很多讲究,可以绑定焦点事件检测所有焦点变化,确定好焦点的顺序很重要
焦点的变化是基于用户的输入和操作触发的,而不是自动传递给父级。
布局
- 对象都先统一创建再配置
- 布局创建由大到小,先创建后布局
- 先加类成员的控件,再增加单纯的显示控件
- 布局应该由外而内去考虑
样式
QStyle 定义了 QT 的绘制方式,有几个重要的虚函数控制了 Widget 样式的装载。
注意:顶级 Widget 没有边框属性,不能配置圆角
<namespace>--<className>表示命名空间- 使用 image 作为主图片,背景图可以选择在内容盒子外面
- 可以通过访问窗口信息,实时修改样式
- qss 可以从外到内写
- 样式 qss 的层级结构可以和界面元素不统一, 这种方式可以更灵活地控制样式
- 可以给 Widget 类编写属性, qss 可以通过属性设置样式
尽量统一名称,这样可以根据名称使用脚本修改,简化 QSS 编写时间
注意:
- qss 中出现未知的线条 bug,可能和设定 fix size 有关系
- 属性变化时候要用 polish 去更新样式
- 可以用 Q_PROPERTY 去实现自定义 Layout 的配置更新
QStyle
从这个层面是底层的绘制接口,可以理解成 Opengl 绘制图形。
- 真正的绘制接口也是 Style。(qss 被设置到 style)
- 但是接口比较底层,动态的变化需要手动配置状态值
QStyleOption
- 定义了基础图元信息结构
技巧
- QSS 控制居中:
qproperty-alignment: 'AlignCenter';- 这个是 QT 内部定义的属性
- 界面布局:
- 代码中间隔全部都设置为 0,使用 qss 实现效果
- 先找到需要边框的元素,剩下的边框全部都设置为 0,然后调整 padding 完成布局。
- 基本配置分类:
- 字体
- 边框、间隔
- 背景
- 大小
- 私有布局可以不重写控件实现很多效果(直接设置父控件不加入布局,使用 qss 调整元素适应布局)
- 命名:
- 先分类再命名,这样代码不会乱
圆角
|
|
渐变颜色
qlineargradient 和 qradialgradient
|
|
实现技巧
移动到屏幕中心
|
|
MVC
委托
视图和数据的代理对象
QStyledItemDelegate
- 可以重写绘制事件,并引用其他控件
- 可以包括样式
- 各种控件的状态(悬浮,点击)
QT Table
表格是复杂但是很常见的控件
积累
- setFocusPolicy(Qt::NoFocus):去除选中虚线框
- data 中有很多数据角色可以控制显示等
tableview 是界面实现中最复杂的一种
- V 通过 QAbstractItemModel 获取数据
- M 通过实现 QAbstractItemModel 定义数据内容
- 数据有不同的角色, 这个要看 V 怎么使用
- 如果 V 先定义, 就要去适应 V 的角色
- 正常是先设计好角色, 然后去实现 V 和 M
- 数据有不同的角色, 这个要看 V 怎么使用
- 然后形成了一个 M 向 V 的数据流方向
多线程
C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock_std::lock_guardstd::mutex-CSDN博客
- std::lock_guardstd::mutex lock(mutex)
要把工作线程和一般线程分开
简单使用是创建一个 QThread, 创建一个工作对象放入线程中, 然后主线程和工作线程通过信号交互.
是否可以对 MVC 进行线程的分离, 把不同层分开多线程提高性能. 或者定义一套规则方式, 形成 多线程/分布式的 MVC.
控件
控件技巧
Frame 绘制横线
|
|
一般控件
Toolbutton
在工具栏上的按钮, 包含一个图标和文本. 可以自定义两者的显示方式. 与一般按钮不同处在于, 可以直接绑定 Action 和 Menu
问题收集
无法将参数 从“QVariant”转换为“QVariant”
原因: QVariant 时没有包含头文件。