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
  • 半模态窗口:冻结用户交互,但是 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 回答:
你提到的依赖问题在软件开发中是一个常见的挑战。要解决这个问题,可以采用以下几种方法:

  1. 依赖注入(Dependency Injection):通过依赖注入,你可以将一个对象的依赖关系从其内部移动到外部,从而减少模块之间的耦合。这通常通过构造函数注入、方法注入或属性注入来实现。这种方式有助于使代码更加灵活和可测试。(属性注入就是直接指针赋值)
  2. 面向接口编程:面向接口编程是一种设计模式,它将关注点从具体的实现转移到接口。通过定义接口,你可以为模块之间的交互定义一组规范,而不是直接依赖于具体的实现。这样做有助于降低耦合度,增加代码的灵活性和可维护性。
  3. 依赖倒置原则(Dependency Inversion Principle):依赖倒置原则要求高层模块不应该依赖于底层模块,二者都应该依赖于抽象。换句话说,模块之间的依赖关系应该通过抽象来实现,而不是通过具体的实现类。这可以通过面向接口编程来实现。
  4. 模块化设计:将系统分解成小的、相互独立的模块,每个模块都有清晰的责任和接口。这样做有助于降低模块之间的耦合度,并且使得修改和扩展变得更加容易。
  5. 依赖查找(Dependency Lookup):这是一种反模式,通常被认为是不推荐的做法。在依赖查找中,一个对象通过查询容器或者其他管理器来获取所需的依赖。虽然这种方法可以减少模块之间的耦合,但它也增加了代码的复杂性,并且可能导致运行时错误。
    在实际开发中,通常会结合使用上述方法来解决依赖问题。这些方法可以帮助你编写更具可维护性、可测试性和可扩展性的代码。`

焦点控制

  1. dumpObjectTree() 函数可用于打印当前窗口小部件及其子部件的树形结构,包括焦点顺序。这对于调试时了解窗口小部件的嵌套和层次结构很有帮助。
  2. dumpObjectInfo() 函数可用于打印关于窗口小部件的信息,包括类名、对象名、父对象等。
  3. focusProxy() 函数返回一个指向窗口小部件焦点代理的指针。通过递归使用该函数,你可以追踪窗口小部件的焦点链。 ```
1
2
3
4
5
QWidget *currentWidget = QApplication::focusWidget();
while (currentWidget) {
    qDebug() << "Widget:" << currentWidget;
    currentWidget = currentWidget->focusProxy();
}
  1. nextInFocusChain()previousInFocusChain(),这两个函数用于获取焦点链中的下一个或前一个窗口小部件。

  2. 还有很多讲究,可以绑定焦点事件检测所有焦点变化,确定好焦点的顺序很重要

    焦点的变化是基于用户的输入和操作触发的,而不是自动传递给父级。

布局

  • 对象都先统一创建再配置
  • 布局创建由大到小,先创建后布局
  • 先加类成员的控件,再增加单纯的显示控件
  • 布局应该由外而内去考虑

样式

QSS自定义属性-CSDN博客

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 调整元素适应布局)
  • 命名:
    • 先分类再命名,这样代码不会乱

圆角

1
控件高度至少为属性值的两倍时,才能看到圆角的变化,也可以用百分制表示radius:`border-radius:50%;`

渐变颜色

qlineargradient 和 qradialgradient

1
"background: qlineargradient(x1:0, y1:0, x2:1, y2:0 ,stop:0 #9ACBF9, stop:1 #3C64D9);

实现技巧

移动到屏幕中心

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	// 获取屏幕的几何信息
	QDesktopWidget* l_Desktop = QApplication::desktop();
	//获取当前屏幕序号
	int current_screen = l_Desktop->screenNumber(this);
	//获取程序所在屏幕的尺寸
	QRect rect = l_Desktop->screenGeometry(current_screen);
	//获取所有屏幕总大小
	QRect rectA = l_Desktop->geometry();
	//获取所有屏幕的个数
	int screen_count = l_Desktop->screenCount();
	//获取主屏幕是第几个
	int prim_screen = l_Desktop->primaryScreen();
	// 计算 widget 应该放置的位置
	int x = (rect.width() - this->width()) / 2;
	int y = (rect.height() - this->height()) / 2;
	// 移动 widget 到屏幕中央
	this->move(x, y);

MVC

委托

视图和数据的代理对象

QStyledItemDelegate

  • 可以重写绘制事件,并引用其他控件
    • 可以包括样式
    • 各种控件的状态(悬浮,点击)

QT Table

表格是复杂但是很常见的控件

积累

  • setFocusPolicy(Qt::NoFocus):去除选中虚线框
  • data 中有很多数据角色可以控制显示等

tableview 是界面实现中最复杂的一种

  • V 通过 QAbstractItemModel 获取数据
  • M 通过实现 QAbstractItemModel 定义数据内容
    • 数据有不同的角色, 这个要看 V 怎么使用
      • 如果 V 先定义, 就要去适应 V 的角色
      • 正常是先设计好角色, 然后去实现 V 和 M
  • 然后形成了一个 M 向 V 的数据流方向

多线程

C++11:互斥锁std::mutex和std::lock_guard/std::unique_lock_std::lock_guardstd::mutex-CSDN博客

要把工作线程和一般线程分开

简单使用是创建一个 QThread, 创建一个工作对象放入线程中, 然后主线程和工作线程通过信号交互.

是否可以对 MVC 进行线程的分离, 把不同层分开多线程提高性能. 或者定义一套规则方式, 形成 多线程/分布式的 MVC.

控件

控件技巧

Frame 绘制横线

1
2
3
QFrame *line = new QFrame(this);
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);

一般控件

Toolbutton

在工具栏上的按钮, 包含一个图标和文本. 可以自定义两者的显示方式. 与一般按钮不同处在于, 可以直接绑定 Action 和 Menu

问题收集

无法将参数 从“QVariant”转换为“QVariant”

原因: QVariant 时没有包含头文件。