Qt Modules

https://doc.qt.io/qt-5/qtmodules.html
Qt的基础模块被称作Qt Essentials,在所有Qt支持的平台都被支持并且经过测试,是Qt的基础。
Qt Core是Qt在C++基础上的扩展,和图形无关,Qt元对象体系、信号/槽机制、集合类等都定义在这里;
Qt GUI是Qt定义的GUI基础类,包含OpenGL支持;
Qt Multimedia是Qt提供的用于实现多媒体功能的GUI类;
Qt Network是跨平台的网络库;
Qt QML是Qt用于支持QML语言和JavaScript语言的类;
Qt Quick是在Qt QML之上的模块,支持以动态的声明式的方式开发UI界面;
Qt Quick Control则是在Qt Quick之上的模块,提供了可在Qt Quick中使用的“Control”,除此之外还有Qt Quick Dialogs,Qt Quick Layouts、Qt Quick Test,都是为支持Qt Quick而存在的模块;
Qt Widgets提供了一系列用于创建桌面应用的UI元素,相比Qt GUI,这个模块更加贴近应用业务,Qt GUI提供的是事件循环和基础的2D/3D绘制能力,而Qt Widgets提供的是在Qt GUI之上实现的控件;

Qt C++基础

内存管理

QObject树形结构

Qt中的每个类都是继承自QObject的,从QObject继承而来的类都要提供一个接受parent指针的构造函数,当parent销毁时,作为child的QObject对象也会被销毁,这就是Qt最基本的内存管理方式。这种内存管理方式要求对象间的关系是一对多的,即一个parent可以有多个children,但一个对象却不能有多个parent,在编写Widget时,一般都是这样的,因为UI界面是树形的。

智能指针

在QObject的parent机制不满足需求时,需要使用Qt提供的智能指针:QSharedPointer,用法和std::shared_ptr类似,注意使用RAII方式避免内存泄漏。
只有在涉及到跨语言通信时,对象的生命周期无法限制于C++时,才考虑使用手动内存管理。

Qt Widgets项目开发

UI Designer

Qt中推荐使用UI Designer来可视化的创建UI界面,UI Designer会生成一个界面对应的xml文件(.ui文件)描述界面布局和央视,并最终使用uic工具来根据xml文件生成界面对应的C++代码,通过阅读uic生成的代码可以学习如何在Qt中手动编程创建UI界面。

基本布局

在Designer中,可以为任意Container Widget指定布局方案,Designer一般有一个根Container,我们一般会直接为根Container指定布局,而不是新建一个专门用于布局的和根Container一样大小的container;
Horizontal Layout和Vertical Layout是基本横向和纵向布局,grid是表格布局,form则是左右两列的表单布局,可以认为是针对此种两列场景经过简化的grid布局。

Item-Based布局

还有一类布局比较特殊,在设计时不能明确指定该布局种各个控件的位置,而是根据数据按照一定的策略进行布局,这里介绍负责进行这些布局的控件:列表控件QListWidger、表格控件QTableWidget、树形控件QTreeWidget。
同Android和iOS开发一样,这样的控件一般会通过一个Adapter关联外部数据,由adapter将外部数据翻译为控件需要的item进行展示,虽然Qt也提供了直接添加item的方法,但一般来说adapter是更常用的:
这里是Qt对于此类布局的开发指引:https://doc.qt.io/qt-5/modelview.html
QAbstractModel

创建自己的控件并在Designer中使用

这是个比较高级的主题,要想创建自己的控件,就首先需要知道:编写一个Qt控件需要注意什么、Qt Designer如何管理控件属性。

Widgets

QWidget类是所有Widget的父类,提供了基础的渲染和处理输入的能力,自定义Widget是通过继承QWidget或它的某个子类并重写virtual方法实现的。

Window和Dialog

如果一个Widget没有被嵌入到另一个Widget内,那么它就是一个Window。不过在Qt中,最常用的应用Window还是QMainWindow以及QDialog的子类。
在很多桌面环境中,不是应用中的所有window都会显示在任务栏中,只有主要窗口会显示,其他附属于主要窗口的其他窗口不会显示;Qt中只有QMainWindow为主要窗口,其他有parent的QWidget虽然可以通过设置Qt::Window标记来显示成窗口,但它们都会被当成是主要窗口的附属窗口。
几乎每个桌面应用都会提供菜单、工具栏、状态栏、可浮动/内嵌的widget,这些都由QMainWindow这个应用的主窗口布局来管理,QMainWindow的中心区域则可以放置任意类型的QWidget,为业务的主要交互区域。
Dialog Window用做展示附属窗口,通过继承QDialog类即可定义Dialog的布局和样式,但一般可以直接使用Qt预先提供好的Dialog来完成大部分任务。

Window几何方法

QWidget提供了一些方法用于读取或设置QWidget的几何属性,其中某些是从Window框架左上角起为原点的,有些是从Window的内容区域左上角起为原点的,不过如果Widget不是Window的话,这个区别就无所谓了。

  • 包含Window框架部分的API:x(), y(), frameGeometry(), pos(), and move()
  • 不包含Window框架部分的API:geometry(), width(), height(), rect(), and size()

在X11上,由于窗口相关的协议过于松散(灵活),而且实现过多,Qt不能保证总是正确的返回结果,不过大部分情况下Qt都已经经过测试了。

应用主窗口

Qt支持很多应用的常见UI模式,这些都是通过QMainWIndow提供的:

  • QMenu / QManuBar - 应用顶部菜单栏
  • QDockWidget - 可漂浮可内嵌的窗口
  • QMdiArea / QMdiSubWindow - 多文档界面,想象一下Sublime的2x2窗口布局方式
  • QSizeGrip - 可以响应应用主窗口大小变化
  • QStatusBar - 状态栏,想象一下Window中的左下角那个状态提示
  • QToolBar - 工具栏
  • QAction / QActionGroup - 菜单栏或工具栏内展示的项目
  • QWidgetAction - 继承自QAction,是允许通过Widget自定义展示内容的QAction

应用对话框

Qt支持很多常见的对话框:

  • QColorDialog / QFileDialog / QFontDialog / QInputDialog / QMessageBox / QProgressDialog - 这些是Qt提供的预制对话框
  • QDialog / QDialogButtonBox - 当自定义对话框布局时需要用到的类,通过为QDialog指定Widget即可方便的展示自定义对话框

Qt Quick项目开发

Qt QML

Qt的QML模块提供了使用QML语言开发应用的方案:定义QML语言规范、提供使用C++或JavaScript扩展QML的API、提供C++ API。
QML只是定义了语言和架构,Qt Quick则提供了显示组件、动画框架、数据绑定支持、以及其他用于构建UI的支持;
虽然QML乍一看很像XML,但它的语法和XML完全没关系,比起XML,它更像是从JavaScript或者JSON之上发展而来的语言。

基础语法简介

QML是一个多范式语言,用于定义对象属性和指定对象如何响应来自其他对象事件。对象的属性和行为都直接通过对象定义表示,定义对象时除了可以定义对象的基本属性,还可以使用脚本语言自定义对象的行为。
QML的基础语法仅以下三类:

  • import表达式
    • import QtQuick 2.0 - 引入预先注册好的QtQuick 2.0;
    • import QtQuick.LocalStorage 2.0 as Database - 同上,将QtQuick 2.0提供的LocalStorage API引入到Database这个名字空间中;
    • import “../privateComponents” - 引入包含QML类型定义的目录
    • import “somefile.js” as Script - 引入js文件
  • 对象定义
    • 对象定义由对象类型后面跟一对大括号以及定义在大括号中的所有属性构成;
    • 对象类型必须首先通过import表达式引入;
    • 对象定义中可以定义child object,当child object为集合时即可构造树形结构,一般允许定义child object的对象都将常用的child object属性设置为默认的,以简化对象定义的写法,如果看到没有写出属性名的属性,就需要查阅文档,找出default property;
    • 在QML语言中有两类父子关系,一类是QML定义时的父子关系,一类是渲染出来的UI组件实际的父子关系(visual parent),通常二者指的是同一个父对象,但C++代码是可以修改后一类关系的,我们在QML中可以使用parent引用,这个parent引用引用的是由后一类确定的父对象;
  • 注释
    • 使用 // 或者 / /定义单行或多行注释;

对象属性

在对象定义大括号中,可以定义如下类型的属性:

  • id
    • 由QML支持的对象基础属性,用于在component scope中唯一标记一个对象,其他对象中可以使用id引用该对象以访问对象属性;
    • 必须以小写字母或下划线开头,不能包含除字母、数字和下划线以外的字符;
    • id属性不可修改,不可通过对象的id引用它并访问它的id属性;
  • Property
    • property可以被赋值为静态确定的值或者被绑定为动态的表达式;
    • property的注册:
      • C++中通过Q_PROPERTY注册;
      • QML中通过 [default] property 语法注册
        • 注册的property会同时定义一个与其关联的signal handler属性:onChanged;
        • 除了Enum以外的QML基础类型、通过import引入的自定义类型都可以用于规定property的类型,var表示任意类型;
      • property命名必须以小写字母开头,并且只能包含字母数字下划线,并且避开JS保留字;
    • property的赋值:
      • 初始化时赋值: :
      • 定义时赋值:[default] property :
      • 命令式语句(通过JS代码)赋值:[.] = value
    • 静态值和绑定表达式值
      • 静态值指的是不依赖其他对象的字面值;
      • 绑定表达式值指的是通过JS代码确定的,依赖其他对象属性计算出来的值,当被依赖的对象属性发生变化时,QML引擎会自动重新计算绑定表达式的值并将新值赋值给属性,这种属性值绑定也被称作Property Binding;(如果不希望获得动态绑定特性,而是只希望获得一次计算结果,可以使用Qt.binding())
    • 特殊类型:
      • list
        • 声明:[default] property list<> propertyName: ,不要被<>这种写法迷惑,将两个符号拆开,一个是list<>,一个是
        • list类型的property赋值时需要使用中括号包围list中的所有值,如果只有一个值可以省略中括号
      • grouped
        • grouped property是由QML语言直接提供的,或者由Qt Quick模块直接提供的类型,这些类型包含一系列子属性,定义这些属性时,可以使用点符号或者使用大括号符号
    • 别名:[default] property alias : ,别名的限制比较多:只能引用对象或定义这个别名的对象中的其他属性、不能包含任意js表达式、不能引用grouped property,等等(值得注意的是别名可以遮盖property,但对象内定义的function可以访问未被遮盖的property,不过qt文档里的例子却很奇怪,按照例子来看,property应该是始终都被遮蔽的,是不是写文档的人当时的理解有问题。。)
    • 默认property:每个类型可以有一个默认property,当在定义对象property时不指定名称,就相当于是在为默认property赋值,Item-based类型在定义children时几乎从来不写出children属性名称,而是直接写Item定义,这些Item都会被添加到children这个list类型的属性中;
    • 只读属性:readonly property :
      • 只读属性必须在声明的同时定义
    • property modifier object:用来定义property value modifier object的,还不清楚这是什么,之后补上
  • Signal
    • 注册:
      • 在C++中,通过Q_SIGNAL注册signal,以这种方式注册过signal的类型在QML中也可以使用
      • 在QML中,通过 signal [([ [, …]])] 注册signal,当参数为空时可以省略参数及括号
    • 监听signal:在注册过signal的对象中定义on函数即可监听signal;
    • 触发signal:每一个注册过的signal都自动成为一个可调用的函数,调用该函数即可触发signal,通知所有监听者
    • property变化signal:每个property注册时QML都自动的创建了“Changed” signal,可以使用onChanged监听;
  • Signal Handler
    • 上面介绍signal时已经介绍过了。。signal handler是特殊的,在signal方法被调用时由QML engine调用的方法,如果不定义signal handler,那么某个signal handler就是一个默认的空实现
  • Method
    • method是一个函数,它可以被connect到某个signal来在signal被触发时自动调用
    • 注册:
      • C++中通过Q_INVOKABLE或Q_SLOT注册,以这种方式注册过的函数在QML中也可用;
      • QML中通过 function ([[, …]]) { } 注册;
    • QML类型定义的method包含JavaScript代码,可以在对象内部调用或者被其他对象调用,method的类型是var,也就是说没有一个专门的函数类型,此外参数也不需要指定类型,这些点和signal不太一样;
  • Attached Property & Attached Signal Handler
    • Attached指的是一种特殊的将原本对象不可用的property或者signal handler提供给对象使用的机制,QML类型在实现中可以在C++中创建attached property或者attached signal handler,在运行时将这些property或者signal附加到特定对象(不要假定这些对象的children中都可以使用Attached property或attached signal),并在QML文档中说明这些特殊property或signal的用途,QML代码中可以通过特殊的方式引用它们;
    • 引用:
      • property:.
      • signal:.on
    • 例子:ListView的isCurrentItem property可以通过ListView.isCurrentItem引用、Component的onCompleted signal可以通过Component.onCompleted注册监听;
  • Enumeration
    • Qt 5.10开始允许在QML中声明enum,声明定义的方式和引用的方式都非常平凡,enum其实就是有名字的整数
    • 声明:enum TextType {Normal,Heading}
    • 引用:property int textType: MyText.TextType.Normal

基础类型

  • QML引擎提供的基础类型
    • bool
    • double
    • enumeration
    • int
    • list
    • real
    • string
    • url
    • var
  • QML的QtQuick模块提供的基础类型
    • date
    • point
    • rect
    • size
  • 需要注意的是某些基础类型本身也是包含property的,但这些基础类型的property不具备一般对象property的自动生成Changed signal的特性,不可以直接注册property changed监听。

JavaScript类型

在QML中,通过var类型即可定义JavaScript的对象或数组,例如:property var theDate: new Date()。
具体来说,JavaScript类型可以出现在如下四种场景:

  • Property Binding
    • 在定义property时使用JS即可自动构造binding,在JS代码中为property赋值时若想保留binding特性则需要使用Qt.binding计算property值;
    • Property Binding使用简单的单行JS代码,QML不建议将过于复杂的计算逻辑放在Property Binding,因为Property Binding涉及到多次执行,这可能导致性能问题,此外还会降低代码可读性,提高维护难度;
  • Signal Handler
  • Standalone Function
    • 在对象中定义的standalone function,这种方式定义的function可以通过对象引用调用
    • 在单独的js文件中定义的standalone function
    • 可以将signal handler连接到standalone function
  • 特别指出的是,有时候希望在初始化阶段执行某些逻辑,QML提供了Component.onCompleted这个attached signal来支持此场景,当这个QML对象已经完成构造后,就会触发该signal,业务得以执行一些初始化逻辑(此外还提供了destruction signal来支持在对象销毁时执行业务逻辑);

自定义类型

  • 通过QML自定义对象类型
    • 在QML中自定义的类型代码必须位于某个目录下的.qml文件中,TypeName必须是一个合法的类型名,这样QML引擎就会识别这个文件中是一个QML类型定义,同一目录下可以不经import直接使用该类型,而不同目录下需要import后使用;
    • 自定义类型中不存在private访问修饰的概念,定义的所有property、signal、method都是可以由使用方定义的;
  • 通过C++自定义类型