十年代码生涯一瞬间,代码即人生!
<返回上一级
Qt-跨平台的C++图形用户界面应用程序框架(一)
iOS前端C++操作系统

韩元旭、余橙、沈开洋

Qt介绍

Qt是一个跨平台的C++图形用户界面应用程序框架。它早在1991年奇趣科技公司两位合伙人着手开发这样一个平台,在2008年如日中天的诺基亚由于看好Qt在嵌入式领域的潜力,一掷千金将它收购作为新一代智能手机操作系统的载体,但是因为诺基亚在智能手机领域的败北,不得已而放手Qt,终于Qt于2012被最后一位东家 Digia 公司收购。经过这几年的发展,Qt不但拥有了完备的C++图形库,而且也极大的提高了Qt开发跨平台应用程序的能力。

Qt可以同时支持桌面应用程序开发、嵌入式开发和移动开发,甚至它可以做移动开发,覆盖了现有的所有主流平台。你只需要编写一次代码,发布到不同平台前重新编译即可。

哪些产品都是用Qt开发的?

Qt凭借一套对原生Windows、Mac、Linux等平台支持很好的 GUI 库和丰富的 API 库,使得它成为了开发跨平台桌面应用的一个很好的选择。与中国一些优秀的桌面端软件选择自己开发多平台的 GUI 库不同,国外的很多优秀桌面应用都偏好采用跨平台的 GUI 库进行开发。比如在硅谷有一款很著名的文档管理应用 DropBox,微软自家的社交聊天工具 Skype,像极品飞车这样大型的游戏的GUI页面,甚至国内的金山软件公司推出的办公软件 WPS Office,当然还有我们的AlphaBox

为什么AlphaBox选择Qt?

因为Qt不仅能够高效率的完成不同平台GUI内容的开发,更能够高效率的完成系统级别的一些任务。这也是AlphaBox选择使用Qt开发的原因。

AlphaBox 其实包含两个主要的进程,一块是同步盘的引擎—C语言构建的底层同步进程,我们称之为 daemon;剩下的图形页面以及与操作系统交互的模块都是使用Qt进行开发的。Qt提供的丰富的跨平台GUI组件能够保证在不同操作系统中 AlphaBox 都有着美观和吻合操作系统的样式,Qt自家生产的 IDE—Qt Creator 提供了一套非常好上手的图形界面构建工具,即使是刚接触Qt的小白也可以轻松的绘制出想要的页面并且能够完成核心页面逻辑。除此之外,得益于 C++ 的加持,Qt与操作系统有着非常健壮的通信机制,凭借这一点,AlphaBox 能够轻松完成精准监控操作系统中文件的改动、建立本地数据库进行写入数据的等操作,不仅于此,优秀的混合编程能力能够让Qt轻松的与 Objective-CC# 等语言进行混编,实现系统级别扩展的调用,这就是我们能够在 FinderWindows 资源管理器中能够看到同步盘文件不同状态的原因了。

Qt优势

优良的跨平台特性

Qt支持 WindowsLinux/UnixMac OS XAndroidBlackBerryQNX等多种平台,并为这些不同的平台提供了统一的开发环境。

面向对象

C++是完全面向对象的,这一点和Objective-c等在开发很相似。而Qt又是基于C++一种语言的扩展,大家都知道C++ 有快速、简易、面向对象等很多优点,所以Qt自然也继承者C++这些的优点。

Qt良好的封装机制使得Qt的模块化程度非常高,可重用性较好,对用户开发来货是非常方便的。Qt提供一种为signals/slots(信号和槽) 的安全类型来替代callback,使得各个元件之间的协同工作变得十分简单。

丰富的API

Qt包括多达 250 个以上的 C++ 类,还提供基于模板的 collections, serialization, file, I/Odevice, directory management, date/time 类。甚至还包括正则表达式的处理功能。
支持 2D/3D 图形渲染,支持 OpenGL。
大量的开发文档。

XML支持

Webkit 引擎的集成,可以实现本地界面与Web内容的无缝集成, 但是真正使得 Qt 在自由软件界的众多 Widgets (如 Lesstif,Gtk,EZWGL,Xforms,fltk 等等)中脱颖而出的还是基于 Qt 的重量级软件 KDE。

信号和槽机制

Qt提供了信号机制用于完成见面操作的响应,是完成任意两个Qt对象之通信机制。其中,信号会在某个特定情况或动作下被触动,槽是等同于接受并处理信号的函数。

为什么方法不是直接调用的。中间用到 Signal机制不是多此一举?

其实在我们生活也是一样,老板级别的好说话,老板给助理分派任务也好说话,但是助理给老板分任务,可想而知会有什么后果,在以前的统治阶层肯定不允许这样的事发生。所以在分层思想中,我们所调用的函数也是这样的,上层可以调用下层和同一层的函数,下层函数不可以调用上层函数,否则程序的层次性会被打破,导致结构错综复杂,难以维护和管理。

那么怎样才能做到向上管理呢,有任务分配给老板怎么办?

老板会设立一个机构,也就是一个函数,用无限循环来查询助理的状态,如果助理真的有事情,这个机构就把这消息拿到老板来处理。但是这种处理方式显得有些复杂,我们想要的简单明了的方式是,如果助理有事件发生,可以直接调用老板函数处理。

说了这么多其实就是想说,信号和槽的最大优势在于,它完善了程序分层的思想,可以在不改变程序的层次性的情况下,完成由下层到上层的调用。在下层发出一个 Signal,这时上层与其想关联的 Slot 函数就会响应。

现在,信号和槽中存在的问题是:

要想解决以上问题,就需要将相应的信号和槽连接起来。当指定的信号发出时,槽所在的对象就能接收到该信号,从而调用相应的槽函数执行指定的处理。

信号与槽的连接方式

1.一个信号可以与另一个信号相连

connect (Object1,SIGNAL(signal1),Object2,SIGNAL(signal2));

表示 Object1的信号1发送可以触发Object2的信号1发送。

2.同一个信号可以与多个槽相连:

 connect (Object1,SIGNAL(signal2),Object2,SIGNAL(slot2));
 connect (Object1,SIGNAL(signal2),Object3,SIGNAL(slot1));   

3.同一个槽可以响应多个信号:

 connect (Object1,SIGNAL(signal2),Object2,SIGNAL(slot2));
 connect (Object3,SIGNAL(signal2),Object2,SIGNAL(slot2));  

4.连接可以被移除:

这种情况用得比较少,因为在对象被删除时,Qt会自动移除与这个对象相关的所有连接。

disconnect(sender, SIGNAL(signal), receiver, SLOT(slot)); 

但是,常用的连接方式为:

 connect (Object1,SIGNAL(signal),Object2,SIGNAL(slot));

其中,signal 为对象Object1的信号,slot 为Object2的槽。

####提示: 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射所接收到的同样信号。
如果一个信号与多个槽相联系的话,那么,当这个信号被发射时,与之相关的槽被激活的顺序将是随机的。
宏定义不能用在 signalslot 的参数中。
信号和槽的参数个数与类型必须一致。

信号和槽机制优点

类型安全

需要关联的信号和槽的签名必须是等同。即信号的参数类型和参数个数 同接收该信号的槽的参数类型和参数个数相同。不过一个槽的参数个数是可以少于信号的参数的个数的,但是缺少的参数必须是信号参数的最后一个或者几个参数。如果信号和槽的签名不符,编译器就会报错。

松散耦合

信号和槽机制大大降低了Qt对象的耦合度。发送信号的Qt对象不需要知道是哪个对象来接收它的信号,它只需要做的是在适当的时间发送一个信号,而且不需要知道也不关心它的信号有没有被接收到,更不需要知道哪个对象的哪个槽接收到了信号。

同样地,Qt对象的槽也不需要关系是哪些信号连接了自己,如果信号和槽连接上了,Qt就能保证了适合的槽得到了调用。即使关联的对象在运行时被删除。应用程序也不会崩溃。

信号和槽的效率

信号和槽机制增强了对象间通信的灵活性,当然在增加灵活性的同时在性能方面也会有一定的损失。同大家回调函数相比,信号和槽机制运行速度有些慢。通常,通过传递一个信号来调用槽函数将会比直接调用直接调用非虚函数运行速度慢10倍。

原因:

  1. 需要定位接收信号的对象。
  2. 安全地遍历所有的关联。
  3. 编组/解组传递的参数。
  4. 多线程的时候。信号可能需要排队等待。

然而,与创建堆对象的new操作及删除堆对象的delete操作相比,信号和槽的运行代价只是它们很少一部分。信号和槽机制导致的这点性能损耗,对实时应用程序是可以忽略的;同信号和槽提供的灵活性和简便性相比,这点性能损耗是值得的。

Qt 布局系统介绍

布局系统

作为一名 iOS 开发人员, 见证着 iOS 布局系统的不断完善, 从绝对布局, Autoresizing 到 Autolayout. 使得开发人员的工作效率越来越高, 项目界面的可读性和易维护性越来越强. 如今 IDE 中的可视化界面工具已经非常强大, 许多网友"戏称" iOS 开发者为"UI 拖拽师", 可见, iOS 开发中界面布局系统的高效. 所以, 优秀的布局系统的使命在于让开发者花更少的时间来完成更易维护的界面.

同样的, 在 Qt 中, 系统提供了强大的排版机制来为窗口中的视图进行布局排版, 经过了对 Qt 布局一个初步的探索, 不得不对 Qt 布局系统的简洁高效而又功能强大表示赞叹.

布局系统的功能

在 Qt 中, 布局系统可以完成

布局系统的结构

Qt 提供了 QLayout 类及其子类来为界面进行排版布局. 结构如下图:

布局系统结构图

QLayout 是布局系统中的抽象基类, 继承自 QObject 和 QLayoutItem, 其中四个子类分别为

在真实使用场景中, 往往需要通过多种布局的相结合来完成界面的设计, 接下来将分别介绍四中布局.

QBoxLayout 箱式布局

箱式布局提供了两个子类分别处理水平(QHBoxLayout)和垂直(QVBoxLayout)两个方向的排版, 可以使视图排成一行或者一列来显示. 简单说, 就是可以让控件进行排排站, 比如在我们的 AlphaBox 中, 顶部的头像, 姓名, 和刷新按钮排成了一排, 这就是水平箱式布局:

什么叫排排站

你以为我要讲一下这个东西如何实现? NO, 我偏偏要以垂直箱式布局为例, 用一个最简单的例子来介绍箱式布局的使用, 首先创建一个基于 QWidget 的界面, 添加我们需要使用的头文件:

#include <QVBoxLayout>
#include <QPushButton>

并在构造函数中添加如下代码

    //  添加两个按钮
    QPushButton *okBtn  = new QPushButton;
    okBtn ->setText(tr("我在上面, 我最牛"));
    QPushButton *celBtn = new QPushButton;
    celBtn->setText(tr("我在下面, 我不服"));

    //  创建一个垂直箱式布局, 将两个按钮扔进去
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(okBtn);
    layout->addWidget(celBtn);

    //  设置界面的布局为垂直箱式布局
    setLayout(layout);

运行看一下效果, 什么? 这就可以运行了? 坐标呢? 尺寸呢? 是的, 没看错...点击运行:

最简单的箱式布局

两个按钮已经一上一下, 乖乖的在垂直方向自己站好了位置, 就是这么强大, 就是这么省心.

QFormLayout 表单布局

强大的 AlphaBox 是很外向的, 可以很轻松的将你的资料分享给其他用户, 当我们分享的时候, 会有这样一个界面:

在 AlphaBox 中共享资料

看到这个界面, 聪明的你可能会说, 这很简单啊, 好几个水平箱式布局就可以实现, 可是, 更聪明的 Qt 提供了更高效的方式帮助你完成这样一个界面, 那就是 QFormLayout.

在我所学习 Qt 所使用的书籍中, 将 QFormLayout 翻译为窗体布局, 我个人认为, 将其翻译为表单布局更为贴切, 因为 QFormLayout 的强大之处正是可以使用最快的速度完成一个用户输入的表单界面的搭建.

那么, 让我们揭开 AlphaBox 的神秘面纱, 看看这样一个界面是怎么实现的.

首先, 拖拽一个 Form Layout 到 Widget 中.

添加表单布局

双击之后即可为表单增加一行.

为表单增加一行

相信大家看到这张图时, 就已经能理解到表单布局是如何使用的, 提供了标签作为用户输入内容的指引, 提供字段类型作为用户输入的控件, 作为 iOS 开发者, 深知这样一个界面的搭建所需要的繁杂的工作量. 当我第一次打开这个界面时, 被这样创建界面的方式所惊呆了.

  1. 按照图中, 创建表单的第一行, 共享给哪个用户的输入框, 可以为输入框填写占位文字.
  2. 双击 Form Layout 创建字段类型为 QComboBox (多选框)的一行. 填写允许的权限内容.
  3. 设置整个 Widget 布局为垂直箱式布局
  4. 在 Form Layout 下拖拽过去一个 Horizontal Layout(水平箱式布局)
  5. 在箱式布局中添加 Horizontal Spacer (水平占位) 后拖拽两个 Push Button 完成界面布局

共享界面的布局

快不快? 快不快! 快不快!!!

同样的, 如果是使用纯代码表单布局的话可以使用addRow()的方法来添加一行.

QGridLayout 网格布局

强大的 AlphaBox 是这样的

事实上, 强大的 AlphaBox 是这样的, 我们可以共享给多个用户, 而且, 下方会有一个列表, 展示共享的用户以及权限列表. 这时, 表单布局就没办法满足我们, 只好另求新欢 QGridLayout - 网格布局.

网格布局顾名思义, 可以将界面分割成行列来进行布局管理, 在每个单元格中来摆放控件. 所以 AlphaBox 分享的界面使用了一个 两行三列 的网格布局来实现的.

QGridLayout - 网格布局

当然, 更更复杂的界面, 用 Qt 布局的效率也是非常高的, 我做了一个外链分享的布局 Demo, 可以将内部资料生成一个下载链接共享给任何人去下载.

外链分享界面

这个界面中, 我在Tab之内使用了网格布局, 布局如图:

外链分享界面布局

从图中可以看出, 网格布局像是在操作一个 Excel 一样简单, 布局单元格, 合并单元格, 等等.

在这个界面中, 更灵活的使用了 QLayout 的属性来完成了界面布局排版.

同样的, 在代码中, 可以使用如下等的 Api 来为网格视图添加一个从几行几列开始占据几行几列的控件:

void addWidget(QWidget *, int row, int column, int rowSpan, int columnSpan)

QStackedLayout 栈布局

如在 AlphaBox 中, 我们可以通过云端文件浏览器直接查看和操作云端文件, 在加载的过程中, 会有一个转菊花的界面.

在转菊花的 AlphaBox

加载失败时的错误提示:

菊花转失败了的 AlphaBox

以及加载成功时:

通常情况下我们能看见的 AlphaBox

通常应用的界面会根据不同的状态有不同的内容, 这时就可以使用 QStackedLayout 栈布局, 栈布局提供了一个页面的栈, 每个页面有完全独立的界面布局. 可以非常清晰的对不同状态下的界面进行布局管理.

在 Qt 的可视化布局工具中, 通过 Stacked Widget 来完成界面的栈布局

Stacked Widget

通过右键来进行页面的插入移除和排序等操作.

最后

在接下来的文章中我们会从搭建项目起,分模块的更加深入的介绍Qt开发,敬请期待!