Day 03 构造基本界面元素

首先我们构建主程序界面。通过基本界面的构建,我们可以进一步理清需求,明确模块接口。

界面草图

严格讲,软件的界面应当做专门设计。如何构筑界面,尤其是如何美化界面,并不是我们在本书中关注的重点。在我们的示例项目中,为了减少无关的内容,我们对内容做了尽可能的简化,只保留和要讨论的内容有关的必要部分。我们也不考虑界面的美观等内容,以实现基本功能为目标,只要界面能显示需要显示的信息就可以了。

我的观点一直是,Qt这个东西,在以相同的成本前提下,要做出一个美轮美奂的界面上面,距离WPF差了十万八千里。在做出一个能用的基本界面的前提要求下,开发成本距离Web技术更是差了几个十万八千里。所以,它的根本点就不应该是在界面的外观上面。本书也不会把精力放在这上面。

下面就是我们打算用来实现的一个简单的界面:

  • 菜单条,工具栏,状态栏等系统组件
  • 整个应用窗口垂直方向分成左中右三部分:
    • 左边自上而下由两个组件:上面是一个显示照片预览图的控件,下面是一个图像化显示扫描工作进度的组件
    • 中间部分是扫描图像显示窗口,我们会用来在扫描过程中显示得到的图片
    • 右边部分会用来实时显示扫描过程中的实时信息结果。这一部分我们暂时不考虑实现,这样,在第一个版本中,我们的窗口就只包含左右两部分。

我们参考在WPF中常用的做法,将一个复杂界面划分成几个用户控件组合实现的做法,而不是把所有的界面元素都堆在主窗口中。这些用户控件都是QWidget的派生类,它们之间,以及和其他的非界面组件之间,通过signal-slot方式通信。主窗口基本上只是负责它们之间的Dispatcher。

我们将左侧的Widget命名为ControlPanelForm,将中间的组件命名为MainClientForm,将右边的组件命名为AnalyseForm。分别在Creator中创建对应的界面类:

ScannerMainWindow中添加两个QWidget,左边一个命名为leftPanel,右边的命名为centerClient。设置leftPanel的最大最小宽度都是400。然后,设置窗口布局为水平布局。

然后将它们提升为我们新建的类型:

首先选择leftPanel,右键鼠标并选择菜单提升为...

在对话框中,在提升的类名称中输入类名ControlPanel,并按下添加按钮,再按下提升按钮

按照同样的方式,将centerClient提升为MainClientForm

添加工具栏

在Creator中打开主窗口,为它增加几个QAction

  • actConnect, 表示连接设备(扫描仪)
  • actSingleScan,表示执行一次手动扫描
  • actStop,表示中断扫描活动
  • actBatchScan,表示执行批量扫描
  • actConfig,表示激活设置窗口

为这几个Action设置好属性,配置好图标。我们是原型机程序,直接从www.iconfont.cn上面寻找现成的图标使用。建立一个新的项目,我们叫QtInPractice吧,然后寻找合适的图标,添加到购物车中,并最终保存到这个项目中。

我们选择图标,选择“下载”,如下图所示:

接下来,可以修改图标的颜色,并选择格式。我们为每个图标下载三种颜色的图标:灰色的,表示按钮不可用,黑色的,表示普通情况,蓝色的,表示按钮被按下的情况。并分别保存下来,并添加到资源定义文件中,并指定给对应的Action去。

CellScanner中创建目录resources,将图像保存进去。

然后,在CellScanner项目中添加新文件,选择文件类型为Qt Resource File,并设置文件名称和位置:

然后将下载的图像添加到资源文件中

然后就可以为每个Action指定图标了。

最后,将Action拖动到工具栏上就可以了。

如果觉得图标大小太小,可以修改工具栏的图标大小,它的默认值是24x24,可以将其改为32x32。

运行程序:

这种软件的运行一般来说是会占满整个屏幕运行的,我们只需要在构造函数中添加一行就可以:

1
2
3
4
5
6
7
8
ScannerMainWindow::ScannerMainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::ScannerMainWindow)
{
ui->setupUi(this);

setWindowState(Qt::WindowMaximized);
}

使用图标字体

我们在前面的工具栏中使用的是图像文件,前端软件现在更流行的是使用图标字体。Qt也可以支持图标字体,只是需要通过代码实现。

我们仍然是选择刚才的iconfont工程,在网页上选择下载至本地,最终会得到一个download.zip的文件。解出里面的iconfont.tff文件,添加到资源文件中。

然后,对于这几个Action,需要清除掉它们的icon属性,然后在ScannerMainWindow的构造函数中:

1
2
3
4
5
6
7
8
9
auto fontId = QFontDatabase::addApplicationFont(":/iconfont.ttf");
auto fontFamilies = QFontDatabase::applicationFontFamilies(fontId);

QFont font;
font.setFamily(fontFamilies.at(0));
font.setPointSize(20);

ui->actConnect->setFont(font);
ui->actConnect->setText(QChar(0xe60a));

在这里,0xe60a来自网页上面这个字体的值&#ex60a;。我们只需要将其改为C的十六进制就可以了。这是一个WORD。

设计ContrlPanel

我们将主窗口左边的内容称为Control Panel,虽然目前仅仅是做状态显示。以后我们也可以向里面增加控制对象。就目前而言,它会在里面显示一个图像控件和一个表示进度的控件。

在Qt中,用于显示图像的最简单的方式是使用一个Label。对于我们的软件来说,在一次扫描过程中,预览图是一个静态的图像,只需要更新一次,作为界面原型,使用QLabel来显示这种静态图像是最合适的。

接下来是一个显示进度的控件。我们先考虑这里的“进度”是什么。对低倍扫描,扫描的是一个矩形范围,体现出来的是MxN个扫描图像。对高倍扫描,则分成两种情况:如果是连续扫描,那么和低倍扫描一样是一个矩形,如果是离散扫描,则是一个个的视野。要表示进度,从最简单的文字描述扫描了多少个视野到复杂的三维图像显示都可以。我们选择一种比较简单的方式,使用一个表格来表示,一个单元格就表示一个扫描的图像。这样,我们可以演示一下QTableWidget的使用。

构造界面元素,从上向下依次放置几个元素,并将布局设置为Vertical:

  • previewLabel QLabel 显示预览图的标签
  • previewImage QLabel 显示标签
  • progressLabel QLabel 文本显示标签
  • progressWidget QTableWidget 显示进度
  • verticalSpacer Spacer 弹簧

所有的Label的alignment属性都设置为AlignHCenterAlignVCenter
previewLabel的修改属性:

  • sizePolicy/Horizontal policy:Expanding
  • sizePolicy/Vertical Policy:Fixed
  • minimumSize/Height: 300
  • maximumSize/Height: 300

预览图采集相机的分辨率为2592x1944,我们根据图片的长宽大致给出一个300的高度值来。

progressWidget的修改属性:

  • sizePolicy/Horizontal policy:Expanding
  • sizePolicy/Vertical Policy:Fixed
  • minimumSize/Height: 200
  • maximumSize/Height: 400
  • verticalScrollBarPolicy: ScrollBarAlwaysOff
  • horizontalScrollBarPolicy:scrollBarAlwaysOff
  • selectionMode: NoSelection
  • horizontalHeaderVisible: false
  • verticalHeaderVisible: false
  • horizontalHeaderMinimumSectionSize: 0
  • verticalHeaderMinimumSectionSize: 0

这样,ControlPanelForm的界面就设计完成了。

接下来看它实现的功能。就目前看,它接受几种信息:

  • 拍摄完预览图后,需要显示预览图和标签内容
  • 计算出低倍扫描区域后,需要在预览图上显示扫描区域范围
  • 开始扫描时,需要在进度区显示扫描的行列数
  • 扫描过程中,需要更新进度

这些接口都是公共的slot函数,会在ScannerMainWindow中和相应的signal连接起来。当前我们暂时不实现。

设计MainClientForm界面

ImageShowForm用于在扫描过程中显示采集的图像。采集图像包括两种:一种是对焦过程序列,一种是最终采集的图像。我们显示对焦过程序列是为了便于在扫描过程中观察到扫描异常的情况,同样,显示最终扫描结果是为了实时看到。两者都是可测试性的要求。

显示图像有两种常用的做法。一种方法,和前面在ControlPanelForm中的做法,使用QLabel控件来显示,另一种我个人更喜欢的是使用QGraphicsView框架。使用一个QGraphicsView控件,并将一个QGraphicsPixmapItem添加进去。

这里我们会使用QGraphicsView来显示图像。

打开imageshowform.ui,在里面添加一个QGraphicsView,命名为imageView,并将Form的布局设置为Vertical,让imageView填满整个页面。

ImageShowForm和外部也只有一种交互:在扫描过程中,当拍摄了一张照片后,需要传递过来显示。我们将接口也留到后面定义和实现。