5.5 显示类的控件

本节显示类控件是指作为显示用途的控件,本节先对 Qt 设计师里面的显示类控件做个简要介绍,然后详细讲解 QLabel 的功能,因为标签控件是最常用到的。显示类的控件有简单的,也有很复杂的,例子代码是展示简单显示控件的用途,本节有两个示例,一个是用标签控件显示图片的例子,第二个是 电子钟的例子。

5.5.1 显示控件概览

在 Qt 设计师左边控件列表里可以看到如下图所示的显示控件:
display
下面概括介绍图上标出的各个显示控件:
(1)标签控件 QLabel
标签控件毫无疑问是最常用的,本教程第二章都是拿标签控件作为显示的例子,但之前只是用,没有全面地介绍这个控件类,我们在后面 5.5.2 节专门讲 QLabel。

(2)丰富文本浏览控件 QTextBrowser
QTextBrowser 就是 QTextEdit 的只读版本,并且添加了额外的浏览器的功能,这个类在 5.3.3 节已经讲过了。

(3)图形视图 QGraphicsView
这是专门用于绘制图形的视图类,它的内部工作原理也是遵循 View/Modal 框架的,QGraphicsView 类是属于视图显示的部分,而内部模型是 QGraphicsScene,绘图场景类内部可以添加各种图形,如线条、矩形、多边形,另外还可以自定义绘图条目 QGraphicsItem,本教程有图形图像专题,等到该专题的章节再详细介绍。

(4)日历控件 QCalendarWidget
前面 5.4 节讲过日期编辑器有自动弹出日历的功能,弹出的其实就是 QCalendarWidget 日历控件,QCalendarWidget 一般只用于日历显示。日历控件常用的属性是 selectedDate,就是选中的高亮的日子,该属性访问函数为:
QDate selectedDate() const
void setSelectedDate(const QDate & date)
用户是可以从日历界面自己选中日子的,选中日子变化时,发送信号:
void selectionChanged()
日历控件默认是不显示日子列表的网格,可以通过属性 gridVisible 改变是否显示网格的特性,访问函数为:
bool isGridVisible() const
void setGridVisible(bool show)
日历控件因为有一个月的日子列表,比较占界面的空间,如果涉及到日子编辑,建议用日期编辑器,日期编辑器也是可以弹出日历的,日期编辑器占的地方也比较小。

(5)LCD 数字显示 QLCDNumber
这里的 LCD 数字显示控件就是比较传统的电子表、计算器等液晶显示器,也俗称 888 显示器,每个数字都是通过部分 8 的笔画来显示。QLCDNumber 的属性及设置函数、描述如下表所示:

属性 设置函数 描述
digitCount void setDigitCount(int) 数字的位数限定,这个控件类不限定数值大小,只限定数的位数。
segmentStyle void setSegmentStyle(SegmentStyle) 设置笔画线段的显示风格。SegmentStyle 是枚举类型,有三种,默认是QLCDNumber::Filled
intValue 和
value
void display(int num)
void display(double num)
void display(const QString & s)
既可以获取整型数值 intValue(),也可以获取浮点数值 value()。数值的设置函数不叫 set*,而是 display() 函数。
smallDecimalPoint void setSmallDecimalPoint(bool) 小数点的显示方式,默认情况下该属性为 false,小数点会占一个数字位。如果设置函数参数为 true,小型小数点就显示在数字之间的缝隙,而不会单独占一位。
mode void setMode(Mode) 设置显示的数字类型,比如十进制、二进制等,默认是十进制 QLCDNumber::Dec。

关于上面属性表格,再说明一下,属性的设置函数上面列举了,而属性的获取函数与属性名一样,所以没列。

setSegmentStyle() 函数用于设置数字笔画的显示风格,显示风格 SegmentStyle 有三个枚举常量:
①QLCDNumber::Outline,用背景色绘制笔画,这个显示风格其实看不清数字,不建议用。
②QLCDNumber::Filled,用背景色填充每个笔画的边框,笔画内部用前景色绘制。在控件比较小时,每个数字位也比较小,笔画比较细,这个显示风格就 看不清数字,因为几乎全是用背景色画笔画。如果控件比较大,那么前景色绘制的部分就比较清晰。如果控件比较小,也不建议用这个风格。
③QLCDNumber::Flat,笔画全部用前景色填充,笔画的边框也是一样的前景色,所以称为扁平风格,这个显示风格最推荐使用,笔画非常清晰。

LCD 数字显示控件的数值,有两个属性,intValue 是整数类型,value 是双精度浮点数类型,这个控件类不限定数值的大小,只限定显示的数字位数。因为是数字显示控件,不是编辑控件,它的显示通过三个 display() 函数设置,使用 QString 为参数的 display() 函数,除了显示数字和小数点,还能显示一些能支持的字符,比如 字母O/数字0、S/5、g/9、负号、小数点、十六进制数、冒号、度数(°)等。

LCD 数字显示控件的工作模式 mode 有四种,QLCDNumber::Hex 是十六进制、QLCDNumber::Dec 是十进制、QLCDNumber::Oct 是八进制、QLCDNumber::Bin 是二进制,默认的模式是十进制的。QLCDNumber 的功能基本围绕以上属性展开,弄清楚属性之后,使用该类控件就比较简单了。

(6)进度条 QProgressBar
进度条在文件的网络上传和下载过程中经常见到,因为某一个事情比较耗费时间,需要用进度条显示该事件完成的进度,一般显示进度的百分比,比如 78% 。进度条 QProgressBar 常用的属性、设置函数和描述如下表(不常用的略过了,详细情况可以查看帮助文档):

属性 设置函数 描述
orientation void setOrientation(Qt::Orientation) 显示的方向,Qt::Horizontal 是水平进度条(默认值),Qt::Vertical 是垂直进度条。
format void setFormat(const QString & format) 设置显示文本的格式,默认格式是 "%p%",就是百分比显示,如 78%
textVisible void setTextVisible(bool visible) 决定是否显示百分比的文本,一般是显示的,例外的是苹果系统风格从不显示进度条文本的。
maximum void setMaximum(int maximum) 进度条数值的上限。
minimum void setMinimum(int minimum) 进度条数值的下限。
value void setValue(int value) 进度条当前数值。
text 进度条文本通过 format 属性设置,可以获取文本 text(),但不能直接设置文本。

首先要明确的是进度条的数值设置,默认下限是 0,默认上限是 100,这样当数值 value 为 24 时,显示的文本就是 %24 ,通过下限、上限、当前数值来计算需要显示的百分比文本,而不能直接设置显示的文本。
数值的范围由 setMaximum() 和 setMinimum() 函数限定,或者用如下范围设置函数一次设置:
void setRange(int minimum, int maximum)

显示的文本格式通过 setFormat() 函数来控制,参数字符串默认是 "%p%" ,意义为:
第一个 "%p" 是格式串占位符,指计算百分比数值,第二个 "%" 就是指百分号本身,这样显示的就是如 "24%" 字样。
如果希望显示当前数值本身,而不是百分比,那么用格式串占位符 "%v" ,就代表当前的 value 值。
另外还有一个格式串占位符是 "%m" ,代表上限减去下限的差额。

三个格式串占位符是可以混搭的,比如 "%v/%m" 就是既显示当前值,也显示总差额,比如显示为 "24/100" 。
除去三个占位符的其他字符,会照原样显示,比如 / 字符。再比如 "%v MB/%m MB",实际显示为 "24 MB/100 MB" 。

默认是显示文本的,调用函数 setTextVisible(false) 就会不显示文本,只显示进度条本身。
orientation 属性决定进度条的显示方向,水平进度条最为常见,对于垂直进度条的文本显示,有个例外的属性 textDirection,这个 textDirection 只对垂直进度条有效,表示垂直进度条的文本显示方向,其设置函数为
void setTextDirection(QProgressBar::Direction textDirection)
QProgressBar::TopToBottom 表示从顶部到底部显示文本,QProgressBar::BottomToTop 是从底部到顶部显示文本。

(7)线条 Line
线条控件就是纯粹作为分隔用途的,比如有多个用途的控件,可以用线条 Line 分隔一下。
水平线条和垂直线条的类虽然在设计师里显示的类名叫 Line,实际上没有这个 Line 类。
线条其实是用 QFrame 类模拟实现的,水平线条代码举例如下:
line = new QFrame(Form);
line->setObjectName(QStringLiteral("line"));
line->setGeometry(QRect(60, 60, 118, 3));
line->setFrameShape(QFrame::HLine);
line->setFrameShadow(QFrame::Sunken);
参数里的 Form 是父窗口指针,QFrame::HLine 就是画水平线条,QFrame::Sunken 是指绘制一个凹下去效果的三维阴影。
垂直线条的代码举例如下:
line_2 = new QFrame(Form);
line_2->setObjectName(QStringLiteral("line_2"));
line_2->setGeometry(QRect(70, 100, 3, 61));
line_2->setFrameShape(QFrame::VLine);
line_2->setFrameShadow(QFrame::Sunken);
水平线条和垂直线条属性 orientation 的取值不一样,Horizontal 是水平线条,Vertical 是垂直线条,其他都是类似的。Qt 设计师会根据线条的方向、位置、粗细,计算出线条占用的矩形,然后设置给 QFrame 对象。
QFrame 是专门用于绘制边框的类,一般有边框特效的控件就是从 QFrame 派生的,本节标签控件、进度条控件、LCD 数字显示控件 的基类就是 QFrame。

(8)OpenGL 三维绘图控件 QOpenGLWidget
QOpenGLWidget 这个控件非常新,是从 Qt 5.4 版本才开始有的,这个控件用于在普通窗体里面进行 OpenGL 绘图。本教程有专门的图形图像专题,等到该专题的 OpenGL 3D 绘图章节再介绍这个控件。

(9)QtQuick 1 控件 QDeclarativeView
这个控件是从 Qt 4 保留而来的,目前 QtQuick 1 已经弃用了,所以不建议用这个控件了。在 Qt 设计师独立程序能看到这个控件,而在 QtCreator 设计模式已经没有这个控件了。

(10)QtQuick 2 控件 QQuickWidget
QQuickWidget 是 QtQuick 2 集成到普通窗体程序里的控件,是目前建议使用的控件。因为本教程暂时没有打算编写 QtQuick 2 和 QML 相关内容,所以也不讲这个控件。关于这个控件的使用方法请参考 QQuickWidget 帮助文档。

(11)网页浏览视图 QWebView
之前介绍过 QTextBrowser 是一个不完全的浏览器,而 QWebView 则是一个完整的浏览器控件,支持 HTML 全部特性,当然也有网络访问功能。QWebView 位于 Qt 的 webkitwidgets 模块里,使用该控件需要添加该模块到 *.pro 文件里面。在 Qt 5 之后,网页浏览相关的控件和类都基于 Chrome 核心了,所以效率是比较高的。在本教程的网络专题里面,专门讲这个浏览器控件。

5.5.2 QLabel 类

之前用过很多次标签控件,比如显示丰富文本,利用 Qt Style Sheet 设置前景色、背景色,为伙伴控件设置快捷键等等,这里系统地把 QLabel 类讲解一下。
QLabel 主要用于显示文本和图片,图片既可以是静态的 BMP、JPG、PNG 等,也可以是动态图格式如 GIF、MNG。标签控件是单纯用于显示的,没有编辑文本等用户交互功能,它的基类是 QFrame,意味着可以设置丰富的控件边框。
QLabel 的构造函数有两个:
QLabel(QWidget * parent = 0, Qt::WindowFlags f = 0)
QLabel(const QString & text, QWidget * parent = 0, Qt::WindowFlags f = 0)
parent 是父窗口指针,f 是标签控件的窗口标志位,在 Qt 帮助文档关于 QLabel 构造函数详细内容里,可以点击 Qt::WindowFlags 链接,有很多的窗口标志位,对应不同的显示效果和功能,具体参考相应的帮助文档,这里不列举了。
第二个构造函数的 text 就是显示的文本字符串。

QLabel 可显示的内容是很丰富的,下表是从 Qt 帮助文档取出来并翻译的,展示了各种内容的设置函数:

内容 设置方法
普通文本 传递 QString 字符串给 setText() 函数。
丰富文本 传递含有 HTML 标记的 QString 字符串给 setText() 函数。
像素图 传递一个 QPixmap 对象给 setPixmap() 函数。
短片 传递一个 QMovie 对象给 setMovie() 函数,QMovie 仅支持动态图,不是真的电影。
数字 传递一个 int 或 double 变量给 setNum() 函数,会显示普通的数字字符串。
等同于设置一个空字符串,也可以用函数 clear() 清空所有内容。

(1)文本设置
首先从文本相关的函数开始,setText() 函数根据参数里的 QString 字符串内容,自动判断有没有 HTML 标记,如果是 HTML丰富文本,就按照丰富文本格式显示,否则按照普通无格式文本显示。除了自动判断文本格式,还可以用
void setTextFormat(Qt::TextFormat)
明确指定当前文本格式,可选的有三种格式,默认是 Qt::AutoText,自动判断;如果设置为 Qt::PlainText,就是强制作为普通无格式文本显示;如果设置为 Qt::RichText,那就一定作为丰富文本格式显示。

对于丰富文本里带有的超链接文本,默认情况下 QLabel 是不会根据用户点击打开超链接的,如果希望用户点击超链接之后,自动用系统浏览器打开超链接,那么可以设置 openExternalLinks 属性为 true:
void setOpenExternalLinks(bool open)

对于长文本显示,将标签控件设置得足够大,就可以显示多行文本,标签控件默认不会自动换行,要让标签控件的文本换行,有两种方式:一是改变标签的文本内容,手动加 "\r\n" 或者 "<br>" 换行字符;另一种是设置 setWordWrap(true),wordWrap 属性为 true 时,自动根据文本长度换行显示。

(2)像素图设置
然后是像素图 QPixmap 的显示,QPixmap 类是专门用于将图片文件或者用代码绘制的图形显示到屏幕上,凡是各种控件界面显示用到的图标、图片等,一般都先用 QPixmap 加载,然后设置给控件来显示。QPixmap 加载一个图片文件是非常简单的:
bool QPixmap::​load(const QString & fileName, const char * format = 0, Qt::ImageConversionFlags flags = Qt::AutoColor)
参数里第一个 fileName 是文件名,可以用绝对路径,也可以用相对路径。QtCreator 运行生成的程序时,相对路径是目标程序的工作路径,也就是影子构建路径,而不是源代码路径或 exe 文件路径,不清楚的读者请看 2.4 节末尾部分。
参数第二个 format 是图片的格式字符串,可以不设置格式字符串,该函数会自动根据文件扩展名判断图片文件格式。Qt 支持的图片格式很多,主流的 BMP、JPG、PNG、XPM 等都支持,具体请查阅帮助文档索引 Reading and Writing Image Files。
参数里第三个 flags 是像素图的颜色标志,默认的标志位是 Qt::AutoColor,如果图片的一个像素只占 1 bit,说明是黑白图,那么加载后是单色的黑白像素图,如果是彩色图,那就按照桌面设置的默认颜色深度来加载图片。
flags 其他两个可选的标志位:对于 Qt::ColorOnly,就是全部当作彩色图,按照桌面设置的颜色深度(一般是真彩色)加载所有图片;对于 Qt::MonoOnly,就是全部转成单色的黑白图片加载。
load() 函数如果加载成功就返回 true,否则返回 false。加载图片成功之后就可以将 QPixmap 对象设置给标签显示。

(3)短片设置
接下来是关于短片的显示,QMovie 类不是真的可以放电影,它其实仅支持无声的动态图格式,比如常见的 GIF 图,还有一种基于 PNG 的开源 MNG 动态图。因为是动态图,所以不是一次性加载的,没有 load 函数,而是通过设置文件名并播放的形式打开动态图。采取影片播放形式,首先是设置文件名,可以通过构造函数或者设置文件名函数指定:
QMovie(const QString & fileName, const QByteArray & format = QByteArray(), QObject * parent = 0)
void QMovie::​setFileName(const QString & fileName)
构造函数里的 format 也是动态图格式,可以不指定格式字符串,QMovie 会根据文件扩展名自动判断格式。
如果要判断指定的文件是否被支持,那么可以用后面的 error() 信号判断或者用下面函数判断动态图是否可用:
bool QMovie::​isValid() const

设置文件名之后,就可以利用标签控件的 setMovie() 函数把 QMovie 对象设置给标签,然后开始播放:
void QMovie::​start() //槽函数,开始播放
当然播放过程中,可以进行暂停或停止:
void QMovie::​setPaused(bool paused) //槽函数,bool参数控制暂停或继续
void QMovie::​stop() //槽函数,停止播放,下次 start() 会从头播放

播放过程中除了暂停、继续、停止等,还可以进行跳帧:
bool QMovie::​jumpToNextFrame() //跳到下一帧
bool QMovie::​jumpToFrame(int frameNumber) //跳到指定序号的帧
跳帧的时候,有可能没有下一帧或者没有指定帧,跳转失败就会返回 false。
动态图帧的总数可以用如下函数获取:
int QMovie::​frameCount() const
一般情况下,这个函数会返回正确的计数,但是注意,如果有些动态图格式不支持总帧数计数,那么这个函数会返回 0。

获取当前帧号和帧的像素图,使用下面函数:
int QMovie::​currentFrameNumber() const
QPixmap QMovie::​currentPixmap() const
播放过程中有几个重要的信号,如下所示:
void QMovie::​error(QImageReader::ImageReaderError error) //打开动态图出错时发的信号
void QMovie::​finished()  //播放结束信号
void QMovie::​frameChanged(int frameNumber) //当前帧号变化信号
void QMovie::​started()  //开始播放的信号
无论是用 start() 播放短片还是 jumpTo*** 函数跳帧,一定要在这些加载图片帧的函数之前,关联打开出错的 error() 信号,这样才能知道加载图片帧的时候是否出错以及错误原因。QImageReader::ImageReaderError 枚举类型请查看帮助文档,这里不列举了。

动态图比较有意思的是自带循环计数,可以用如下函数获取循环次数:
int QMovie::​loopCount() const
如果返回值为 0,代表仅播放一次,不循环;而对于有些 GIF 图片,会返回 -1,代表无限循环播放。QMovie 还有其他设置播放速度、背景色、拉伸图片大小等函数,这里不列举了。

(4)内容对齐、边框、伙伴设置
上面讲了不少关于 QMovie 的内容,是因为后面例子用到了。现在回到 QLabel 类的介绍,无论是用标签显示文本还是图片,都可以设置内容的对齐方式:
void QLabel::setAlignment(Qt::Alignment)
默认的对齐方式是左对齐和垂直居中,当然也可以设置为其他的对齐方式,水平方向有:
Qt::AlignLeft(左对齐)、Qt::AlignRight(右对齐)、Qt::AlignHCenter(水平居中)、 Qt::AlignJustify(两端对齐)。
垂直方向的对齐有:
Qt::AlignTop(从顶部起)、Qt::AlignBottom(从底部起)、Qt::AlignVCenter(垂直居中)、 Qt::AlignBaseline(基线对齐)。
还有一个常量 Qt::AlignCenter,就是水平和垂直都居中,等同于 AlignVCenter | AlignHCenter。
水平方向和垂直方向的标志为可以用 | 位或运算符同时启用。

标签控件除了设置内部文本或图片的显示方式,因为基类是 QFrame,所以能设置丰富的边框格式,默认的标签是扁平的,没有边框,如果调用如下函数:
void ​QFrame::​setFrameStyle(int style)
就可以改变边框风格,比如 QFrame::Panel | QFrame::Sunken 就是三维凹槽风格的边框。具体的 style 参考基类 QFrame 的帮助文档。

QLabel 最后一部分内容是关于伙伴快捷键的,在 QLabel 文本里面加上如 "&P" 字样,然后把标签控件的伙伴设置为其他控件,就可以为伙伴设置快捷键 Alt+P 。例如:
QLineEdit *phoneEdit = new QLineEdit(this);
QLabel *phoneLabel = new QLabel("&Phone:", this);
phoneLabel->setBuddy(phoneEdit);
 
5.5.3 图片浏览示例

这个示例就是用 QLabel 控件做一个浏览图片的程序,因为图片有可能比较大,需要用到 QScrollArea 包裹标签控件,这样就能利用滚动区域显示大图。对于动态图,需要播放、停止等按钮,并通过一个水平滑动条展示动态图播放进度。大概的功能就是这些,下面开始这个例子的编 写。
打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 imgshow,创建路径 D:\QtProjects\ch05,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图拖入控件:
ui
首先把主界面窗体大小设置为 480*400,这样能容纳较大的标签控件。
界面里第一行是一个标签控件,objectName 为 labelShow,文本内容清空,因为是用来显示图片用的。
标签控件占用矩形 geometry 为 X:10,Y:10,宽度:461,高度:321,占据着窗体最大的一块地皮。
之前提到要用滚动区域显示大图,现在 labelShow 的矩形是为后面代码里的滚动区域对象占据的,利用滚动区域显示大图。

主界面第二行的控件是四个普通按钮,第一个按钮文本是 "打开图片",objectName 为 pushButtonOpenPic;
第二个按钮文本是 "打开动态图",objectName 为 pushButtonOpenMov;
第三个按钮文本是 "播放",objectName 为 pushButtonStart;
第四个按钮文本是 "停止",objectName 为 pushButtonStop。
调整四个按钮,让它们处于对齐状态,均匀分布在水平线上。
主界面第三行控件是一个水平滑动条,objectName 为 horizontalSlider。这个水平滑动条适用于显示动态图播放进度的,因此与右边三个按钮对齐。

主界面就是如图上设置,现在为四个按钮都添加 clicked() 信号对应的槽函数:
slot
添加四个按钮的槽函数之后,保存界面文件,回到代码编辑模式,添加例子需要的代码。
首先是 widget.h 头文件的代码,需要增加新的槽函数和成员变量:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QPixmap>  //像素图
#include <QMovie>   //动态图
#include <QImageReader> //可以打开图片或者查看支持的图片格式

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    //接收出错的信号
    void RecvPlayError(QImageReader::ImageReaderError error);
    //接收播放时帧号变化
    void RecvFrameNumber(int frameNumber);

private slots:
    void on_pushButtonOpenPic_clicked();

    void on_pushButtonOpenMov_clicked();

    void on_pushButtonStart_clicked();

    void on_pushButtonStop_clicked();

private:
    Ui::Widget *ui;

    //像素图指针
    QPixmap *m_pPixMap;
    //动态图指针
    QMovie *m_pMovie;
    //是否为动态图
    bool m_bIsMovie;
    //动态图是否在播放中,如果在播放中,那么循环播放
    bool m_bIsPlaying;

    //清除函数,在打开新图之前,清空旧的
    void ClearOldShow();
};

#endif // WIDGET_H
在 widget.h 头文件新加了三个头文件包含,<QPixmap> 是像素图的头文件,<QMovie> 是动态图(短片)的头文件,<QImageReader> 是图片读取类的头文件,QMovie 类就是使用 QImageReader 读取动态图里一帧帧图像的,如果读取出错,就发出读取错误信号,信号里的参数是 QImageReader::ImageReaderError 类型,因此提前包含 <QImageReader> 。

在 Widget 类声明里,我们手动添加两个公有槽函数,RecvPlayError() 是用于接收动态图播放错误的信号,RecvFrameNumber() 用于在动态图播放时更新水平滑动条。这两个信号的参数不是凭空想象的,而是根据 Qt 帮助文档里面 QMovie 类信号来确定的,要注意查阅 Qt 帮助文档。

Widget 类声明里四个按钮的槽函数是刚才从设计模式添加的,这里不需要变动私有槽函数。然后我们在类声明里面添加私有成员变量:
m_pPixMap 是用于保存像素图 QPixmap 对象的指针;
m_pMovie 是用于保存动态图 QMovie 对象的指针;
m_bIsMovie 用于标识是否处于动态图(短片)浏览模式;
m_bIsPlaying 表示是否处于动态图的播放过程中。
最后一个私有成员函数 ClearOldShow() 用于在打开新图之前,清理旧的像素图、动态图对象,并重置两个标志位变量。

头文件内容就上面那些,下面为源文件 widget.cpp 添加实体功能代码,首先是头文件包含和构造函数部分:
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QFileDialog>  //打开文件对话框
#include <QScrollArea>  //为标签添加滚动区域
#include <QMessageBox>  //消息框

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //初始化成员变量
    m_pPixMap = NULL;
    m_pMovie = NULL;
    m_bIsMovie = false;
    m_bIsPlaying = false;

    //获取标签矩形
    QRect rcLabel = ui->labelShow->geometry();
    //为标签添加滚动区域,方便浏览大图
    QScrollArea *pSA = new QScrollArea(this);   //该对象交给主窗体自动管理,不用手动删除
    //把标签填充到滚动区域里
    pSA->setWidget(ui->labelShow);
    //设置滚动区域占据矩形
    pSA->setGeometry(rcLabel);

    //打印支持的图片格式
    qDebug()<<QImageReader::supportedImageFormats();
    //打印支持的动态图格式
    qDebug()<<QMovie::supportedFormats();
}
widget.cpp 新包含的头文件有四个,调试类的头文件就不多说了,<QFileDialog> 是打开文件对话框的头文件,<QScrollArea> 是滚动区域的头文件,<QMessageBox> 是消息框的头文件。

在构造函数里,首先对四个成员变量 m_pPixMap、m_pMovie、m_bIsMovie、m_bIsPlaying 进行初始化。

然后是关于滚动区域的代码段,获取了标签控件 labelShow 占用的矩形 rcLabel ,
新建了一个 QScrollArea 滚动区域对象,对象指针为 pSA ,pSA 父对象是主窗口,
主窗口销毁时,子对象都会自动销毁,所以后面不需要手动 delete 这个 pSA 指向的对象。
让滚动区域包裹标签,只需要一句  pSA->setWidget(ui->labelShow) 搞定,
滚动区域包裹标签之后,将滚动区域对象的矩形设置为原先标签占的矩形,这样主界面就能看到滚动区域对象了,
标签控件会自动显示在滚动区域内部。
构造函数里最后两句是打印支持的静态图格式和动态图格式。

构造函数讲完了,下面是析构函数:
Widget::~Widget()
{
    //手动清空
    ClearOldShow();
    //原有的代码
    delete ui;
}
析构函数内部调用了私有函数 ClearOldShow(),用于清理像素图对象和动态图对象。通常 Qt 的控件对象不需要自己清理,因为它们有父对象,父对象销毁时,子对象自动销毁。但是这里用到的像素图、动态图是我们自己的 new 的,它们其实没有父对象,所以手动清理了。

私有的清理函数 ClearOldShow() 代码如下:
void Widget::ClearOldShow()
{
    //清空标签内容
    ui->labelShow->clear();
    //像素图不空就删除
    if( m_pPixMap != NULL )
    {
        //删除像素图
        delete m_pPixMap;   m_pPixMap = NULL;
    }
    //如果短片不为空,就删除
    if( m_pMovie != NULL )
    {
        //如果正在播放则停止
        if(m_bIsPlaying)
        {
            m_pMovie->stop();
        }
        //删除动态图
        delete m_pMovie;    m_pMovie = NULL;
    }
    //标志位重置
    m_bIsMovie = false;
    m_bIsPlaying = false;
}
这个清理函数首先调用标签对象的 clear() 函数,清空标签显示的所有内容。
如果像素图指针 m_pPixMap 不为空,那就删除像素图,并将指针置空。
如果动态图指针 m_pMovie 不为空,先判断是否处于播放中,如果在播放中就先停止播放,然后把动态图对象删除,指针也置空。
最后将两个标志位成员变量都重置为 false。
ClearOldShow() 会在析构函数和新打开图片时调用,用于清理旧的像素图和动态图,并重置标志位。

接下来是 "打开图片" 按钮的槽函数:
void Widget::on_pushButtonOpenPic_clicked()
{
    QString strFileName;    //文件名
    strFileName = QFileDialog::getOpenFileName(this, tr("打开静态图片"), "",
                               "Pictures (*.bmp *.jpg *.jpeg *.png *.xpm);;All files(*)");
    if(strFileName.isEmpty())
    {
        //文件名为空,返回
        return;
    }
    //清空旧的图片或短片
    ClearOldShow();
    //打印文件名
    qDebug()<<strFileName;

    //新建像素图
    m_pPixMap = new QPixmap();
    //加载
    if( m_pPixMap->load(strFileName) )
    {
        //加载成功
        //设置给标签
        ui->labelShow->setPixmap(*m_pPixMap);
        //设置标签的新大小,与像素图一样大
        ui->labelShow->setGeometry( m_pPixMap->rect() );
        //设置 bool 状态
        m_bIsMovie = false;     //不是动态图
        m_bIsPlaying = false;   //不是动态图播放
    }
    else
    {
        //加载失败,删除图片对象,返回
        delete m_pPixMap;   m_pPixMap = NULL;
        //提示失败
        QMessageBox::critical(this, tr("打开失败"),
                              tr("打开图片失败,文件名为:\r\n%1").arg(strFileName));
    }
}
先解释一下 QFileDialog::getOpenFileName() 静态函数,这个函数与之前 5.3.4 节 QFileDialog::getOpenFileUrl() 函数类似,本节的 QFileDialog::getOpenFileName() 用于获取真正的文件系统路径,而之前的 QFileDialog::getOpenFileUrl() 是获取文件 URL。 QFileDialog::getOpenFileName() 函数声明如下:
QString QFileDialog::​getOpenFileName(QWidget * parent = 0, const QString & caption = QString(), const QString & dir = QString(), const QString & filter = QString(), QString * selectedFilter = 0, Options options = 0)
parent 是父窗口指针;
caption 是打开文件对话框标题;
dir 是初始显示的文件夹路径,QtCreator 运行程序时默认为工作路径(影子构建路径);
filter 是文件扩展名筛选格式串,与 5.3.4 介绍的是一样的;
selectedFilter 是从多个筛选格式串默认选中一个;
options 是打开文件对话框的选项,一般用不着。
如果用户选择了正确的文件名,就返回有用的文件名字符串,否则返回空串。

在上面的 on_pushButtonOpenPic_clicked() 槽函数里,打开文件对话框的父窗口是主界面 this,标题是 "打开静态图片" ,初始显示的文件路径 "",为空的,就是默认的工作路径开始,QtCreator 运行程序时的工作路径默认就是影子构建路径,筛选格式串为 "Pictures (*.bmp *.jpg *.jpeg *.png *.xpm);;All files(*)" ,这个筛选格式串其实有两个,双分号前面的是正常的图片筛选字符串,双分号后面的是不筛选,所有文件都能看到。

在获取图片文件名 strFileName ,先判断字符串是否为空,如果为空就返回,在文件名不为空的情况下,继续后面代码。
得到确实存在的文件名之后,调用 ClearOldShow() 清空旧的内容,并打印新的文件名。

清空旧的内容之后,我们新建一个 QPixmap 对象,指针存到 m_pPixMap,
然后调用 m_pPixMap->load(strFileName) 尝试加载图片文件:
如果加载成功,那就把像素图设置给标签对象,并将标签对象的矩形设置为与像素图的矩形一样大,并设置两个标志位成员变量都为 false,因为这里是处理静态图显 示的。
如果加载失败,那么删除像素图,将该指针置空,然后弹出消息框提示打开出错。

QMessageBox::critical() 与其他两个 QMessageBox::​information()、QMessageBox::​warning() 是完全类似的,只不过 QMessageBox::critical() 用于提示严重错误,图标一般是个红底 X 号。

静态图片打开并显示的过程是比较简单的,因为不涉及到播放,下面来看看动态图的打开过程。
"打开动态图" 按钮的槽函数内容相对较多,我们拆成三块来讲解,首先是函数头和文件名获取、判断:
void Widget::on_pushButtonOpenMov_clicked()
{
    QString strFileName;    //文件名
    strFileName = QFileDialog::getOpenFileName(this, tr("打开动态图片"), "",
                               "Movies (*.gif *.mng);;All files(*)");
    if(strFileName.isEmpty())
    {
        //文件名为空,返回
        return;
    }
    //清除旧的图片或短片
    ClearOldShow();
    //打印文件名
    qDebug()<<strFileName;

    //新建动态图
    m_pMovie = new QMovie(strFileName);
    //判断是否动态图文件可用
    if( ! m_pMovie->isValid() )
    {
        //不可用
        QMessageBox::critical(this, tr("动态图不可用"),
                              tr("动态图格式不支持或读取出错,文件名为:\r\n%1").arg(strFileName));
        //清除
        delete m_pMovie;    m_pMovie = NULL;
        return; //不可用就直接返回
    }
//待续
槽函数开始位置,也是用 QFileDialog::getOpenFileName() 静态函数获取动态图文件名 strFileName ,这里的筛选格式串 "Movies (*.gif *.mng);;All files(*)" 也是用双分号隔开的两段,双分号之前是普通的动态图筛选串,双分号之后的是不筛选,所有文件都能看到。

得到 strFileName 之后,判断文件名是否为空,如果为空就返回;如果有实际文件名就继续后面代码。
确认有实际文件名之后,用 ClearOldShow() 清空旧的内容,并打印动态图的文件名。
然后根据文件名新建 QMovie 对象,指针存到  m_pMovie。
动态图 QMovie 的使用方式与之前的像素图使用方式不一样,要用动态图的 isValid() 函数判断给定的文件是否可用,
如果不可用就提示错误消息,删除动态图对象,将指针置空,然后返回。
如果动态图可以用,那么继续后面的代码。on_pushButtonOpenMov_clicked() 槽函数第二块代码如下:
    //动态图的总帧数
    int nCount = m_pMovie->frameCount(); //如果动态图格式不支持计数,那么会返回 0
    //打印帧数
    qDebug()<<tr("总帧数:%1").arg(nCount);
    //如果有统计帧数,那就设置滑动条上限
    if( nCount > 0 )
    {
        ui->horizontalSlider->setMaximum(nCount);
    }
    else
    {
        //获取不到帧数,默认当作 100
        ui->horizontalSlider->setMaximum(100);
    }

    //把动态图设置给标签
    ui->labelShow->setMovie(m_pMovie);
    //修改 bool 状态
    m_bIsMovie = true;
    m_bIsPlaying = false;   //还没点击播放开始的按钮
//待续
因为要显示动态图播放进度,所以用动态图对象的 frameCount() 获取总帧数,并打印出来。
有些动态图格式可能不支持总帧数计数,这种情况会返回 0 。
我们接下来对总帧数 nCount 进行判断,如果大于 0,那就把总帧数设为水平滑动条的上限数值,
如果总帧数是 0 或负数,那么把滑动条的上限设置为默认的 100。

设置好水平滑动条之后,把动态图对象设置给标签控件 ui->labelShow->setMovie(m_pMovie),
并修改 m_bIsMovie 为 true,表示处于动态图显示模式,
m_bIsPlaying 为 false,因为用户还没有点击播放按钮,处于未播放状态。

on_pushButtonOpenMov_clicked() 槽函数第三块代码如下:
    //关联播放时的信号
    //播放出错信号
    connect(m_pMovie, SIGNAL(error(QImageReader::ImageReaderError)),
            this, SLOT(RecvPlayError(QImageReader::ImageReaderError)));
    //播放的帧号变化信号
    connect(m_pMovie, SIGNAL(frameChanged(int)),
            this, SLOT(RecvFrameNumber(int)));

    //将动态图片跳转到起始帧
    if(  m_pMovie->jumpToFrame(0) )
    {
        //跳转成功
        //对于打头的帧,设置标签的矩形为帧的矩形
        ui->labelShow->setGeometry( m_pMovie->frameRect() );
    }
    //如果跳转失败,槽函数 RecvPlayError() 会提示出错
}
注意 connect 函数调用是在 m_pMovie 指针有实际存在的动态图对象之后调用的, connect 函数是将源头对象的信号关联到接收对象的槽函数,如果 m_pMovie 没有指向实际源头对象是不能关联的,因此 connect 不是放置在窗体构造函数里,而是放在新建了动态图对象之后的位置。
第一个 connect 是将源头的 error(QImageReader::ImageReaderError) 信号关联到主窗体的 RecvPlayError(QImageReader::ImageReaderError)  槽函数,用于接收动态图读取和播放过程中可能出现的 错误。
第二个 connect 是将源头的 frameChanged(int) 关联到主窗体的 RecvFrameNumber(int) 槽函数,用于接收动态图播放过程中帧号的变化,将播放进度同步显示到水平滑动条上。

最后的 if(  m_pMovie->jumpToFrame(0) ) 是跳转到动态图的初始帧,如果跳转成功,那么将标签控件的矩形设置为 与动态图帧的矩形一样大。
如果跳帧失败,那么会触发 error(QImageReader::ImageReaderError) 信号,由对应的槽函数去处理。

"打开动态图" 按钮的槽函数代码就是上面的三段,打开动态图之后,没有自动播放,需要用户点击才能开始播放。
下面来看看 "播放" 按钮的槽函数:
//播放开始按钮
void Widget::on_pushButtonStart_clicked()
{
    if( ! m_bIsMovie)   //不是动态图
    {
        return;
    }
    if( m_bIsPlaying )  //已经在播放了
    {
        return;
    }
    //播放动态图
    m_bIsPlaying = true;    //开始播放状态
    m_pMovie->start();  //播放
    //打印动态图默认的播放循环轮数,0 代表不循环,-1 代表无限循环
    qDebug()<< tr("循环计数:%1").arg( m_pMovie->loopCount() );
}
在该槽函数里面,先判断 m_bIsMovie 标志位,是不是动态图,如果不是动态图立即返回;
如果是动态图就继续后面代码。
然后判断  m_bIsPlaying  标志位,如果之前已经在播放了就不处理,直接返回;
如果不是处于播放状态,那么继续后面代码。

进行播放操作时,先将标志位 m_bIsPlaying 设置为 true,然后调用 m_pMovie->start() 开始播放。
该槽函数最后一句是打印动态图里指定的循环播放次数。

与 "播放" 按钮相反的是 "停止" 按钮,停止播放的槽函数为:
//停止播放按钮
void Widget::on_pushButtonStop_clicked()
{
    if( ! m_bIsMovie)   //不是动态图
    {
        return;
    }
    if( ! m_bIsPlaying) //没有处于播放状态
    {
        return;
    }
    //停止播放
    m_bIsPlaying = false;
    m_pMovie->stop();
}
"停止" 按钮槽函数里,也是先对动态图显示的标志位 m_bIsMovie 进行判断,不是动态图就返回。
如果是动态图模式,那么判断是否在播放进行中,m_bIsPlaying 为 false,说明没播放,就直接返回。
如果正好处于播放进行中,那么将 m_bIsPlaying 设置为 false,然后调用 m_pMovie->stop() 停止播放。
播放停止之后,如果下次点击 "播放" 按钮,那么会直接从打头的帧重新播放。

接下来是如果播放过程中出现读取错误,那么会触发 QMovie::error(QImageReader::ImageReaderError) 信号,下面的槽函数就会被调用:
//接收播放错误信号
void Widget::RecvPlayError(QImageReader::ImageReaderError error)
{
    //打印
    qDebug()<<tr("读取动态图错误的代码:%1").arg(error);
    //提示播放出错
    QMessageBox::critical(this, tr("播放出错"),
                          tr("播放动态图出错,文件名为:\r\n%1").arg(m_pMovie->fileName()));
    //回到停止状态
    m_bIsPlaying = false;
}
因为播放过程中出错,所以播放会自动停止,直接执行这个接收错误信息的槽函数。
该槽函数先打印错误代号,这个代号可以从 QImageReader 类的文档查到,目前共有有 5 种可能的错误,这里不列举了。
然后是用消息框提示播放动态图出错的信息。
最后将播放标志位 m_bIsPlaying 设置为 false。

如果播放过程中没出错,那么皆大欢喜,我们可以接收帧号变化的信号,通过水平滑动条显示播放进度:
//接收帧号变化信号
void Widget::RecvFrameNumber(int frameNumber)
{
    ui->horizontalSlider->setValue(frameNumber);
}

整个例子代码就是上面那么多,这个图片浏览例子是既支持静态的图片,也支持 GIF 等动态图。下面生成并运行例子,看看效果。例子用到的图片可以从教程代码文件夹的网址下载:
https://lug.ustc.edu.cn/sites/qtguide/QtProjects/ch05/imgshow/
两张图,opensuse.png 和 cat.gif,放到例子代码文件夹一块。

运行例子后,可以看到带有滚动区域的效果:
run
其实标签控件和封装它的滚动区域 QScrollArea 对象尺寸是一样大的,由于滚动区域自带滚动条,占去了两块地皮,所以标签控件没有完全显示出来,通过拖 动滚动条才能看到标签控件全貌。
在输出面板可以看到 Qt 支持的静态图片格式非常丰富,能报出名字的应该都支持。动态图格式本身很少,这里支持 gif 和 mng 的动态图。

点击 "打开图片" 按钮,打开本节的 opensuse.png ,由于这张图比较大,所以需要滚动浏览的图片区域也多:
run2
如果有读者记性比较好,5.3.4 节 simplebrowser 例子文件夹有一张 opensuse.jpg,读者可尝试打开旧的 opensuse.jpg 图片,会出现如下错误:
run3
都是图片,opensuse.png 能打开,opensuse.jpg 打开出错,这是为什么呢?
用这两张图片举例,不是为了说明 png 格式比 jpg 好,也不是为了说明我们例子代码有问题,
而是为了说明有些错误不在程序本身!
我们举例的 opensuse.png 和 opensuse.jpg 两张图片文件的数据是一模一样的,就是复制了一份,改了个扩展名而已。
opensuse 的图片原本就是 PNG 格式的,在下载的时候错误地保存为 opensuse.jpg。虽然操作系统的图片查看器、之前的 QTextBrowser 示例都能浏览这个错误扩展名的图片,但是 QPixmap::load() 函数根据 jpg 扩展名加载 opensuse.jpg 时出现了错误,因为这张图根本不是 jpg 图片。
这里打开 opensuse.jpg 出错,就是因为图片扩展名错了,与程序本身没关系。

那么 5.3.4 QTextBrowser 示例怎么能正确显示 opensuse.jpg 呢?那是因为它没有用 QPixmap::load() 函数加载图片,而是 QImage::​loadFromData() 函数,感兴趣的读者可以看看 QImage::​loadFromData() 函数帮助文档,它是直接从文件数据头判断文件格式,而不管扩展名是什么,因此能正确显示扩展名错误的图片。

看完静态图浏览,接着看看动态图浏览过程,点击 "打开动态图" 按钮,打开 cat.gif 动态图,先看到初始帧:
run4
滚动区域里的标签控件大小是根据图像帧的大小自动调整的,这张猫图比滚动区域小,不需要滚动条,就自动隐藏了。
我们在打开动态图的代码里跳到了第 0 号帧,所以能看到打头的帧图像。
读者可以点击 "播放" 、"停止" 按钮测试一下动态图的播放效果,这里不截图了,与常规的影片播放差不多,美中不足的是水平滑动条只能显示播放进度,不能进行 快进跳帧而已。
cat.gif 默认是无限循环播放的,一轮结束了会自动从头播放,不需要操作。

5.5.4 电子钟示例

上一个例子一堆内容,其实只示范了标签控件的功能。本小节的例子主要展示日历控件 QCalendarWidget、LCD 数字显示 QLCDNumber、进度条 QProgressBar ,利用这三种控件做一个日期、时间显示的例子,就是一个电子时钟。

重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 timeshow,创建路径 D:\QtProjects\ch05,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图拖入控件:
ui
首先把主窗体大小调整为 400*300,方便容纳内部的控件。
窗体内部有两行控件,第一行的是 LCD 数字显示控件和进度条控件,
LCD 数字显示控件的对象名 objectName 为 lcdNumber,占据的矩形为
X:10,Y:10,宽度:341,高度:95,其他属性如下图所示:
LCDNumber
显示位数 digitCount 为 8,其中小时占 2 位数,分钟占 2 位数,秒钟占 2 位数,还有 2 个冒号分隔。
segmentStyle 是选择扁平风格 flat,这种笔画最清晰。数值设置为 8 个 8 。

进度条控件的对象名 objectName 为 progressBar,占位矩形为
X:360,Y:10,宽度:31,高度:95,其他属性如下图设置:
progressBar
最小值是 0,最大值是 9,当前 value 也是 9,
文本是否能看见的属性 textVisible 的勾选取消,不显示文本,
orientation 属性选择 Vertical,垂直进度条。
这个进度条的用途是显示秒钟的个位数循环,从 0 到 9 ,再从 0 到 9。
之所以将垂直进度条高度设置为 95,是因为刚好放下 9 个进度格子,对应 1 到 9 秒。

第二行也是两个控件,左边日历控件的对象名 objectName 为 calendarWidget,占据的矩形为
X:13,Y:110,宽度:341,高度:171,日历控件其他属性不用修改。

第二行右边的按钮对象名 objectName 为 pushButtonToday,占据的矩形为
X:360,Y:110,宽度:31,高度:171。
按钮并没有方向属性,不能设置为水平或垂直的,图上的按钮仅仅是将文本设置为四行而已,
点击按钮 text 属性编辑框右边的 "..." 小按钮编辑按钮的文本如下图所示:
button
把按钮宽度设置比较窄,文本一个字占一行,那么看起来就像是一个垂直按钮了。

界面就是如上设置,因为图中有三个控件纯粹作为显示用的,不需要关联 LCD 数字显示、进度条、日历的信号,
只需要为 "回到今天" 按钮添加一个普通的 clicked() 信号对应的槽函数即可:
slot
添加槽函数之后,保存界面文件,回到代码编辑模式,下面开始添加例子的代码。

编辑 widget.h 头文件,添加定时器类头文件包含以及成员变量、接收定时器信号槽函数:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer>   //定时器类

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

public slots:
    //接收定时器信号的槽函数
    void RecvTimer();

private slots:
    void on_pushButtonToday_clicked();

private:
    Ui::Widget *ui;
    QTimer *m_timer;
};

#endif // WIDGET_H
头文件 <QTimer> 是定时器类,定时器可以每过一定的时间间隔触发 timeout() 信号,这样我们的电子钟就能每秒更新时间了。
Widget 类声明里添加了一个公有槽函数 RecvTimer(),用于接收定时器的信号更新时钟。
Widget 类声明里还添加了一个私有成员指针 m_timer,用于保存定时器对象。

头文件 widget.h 代码就是上面那些,接下来是编辑 widget.cpp 的内容,添加功能代码,首先是头文件包含和构造函数的内容:
#include "widget.h"
#include "ui_widget.h"
#include <QDateTime>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);    
    //窗体标题改为电子钟
    this->setWindowTitle(tr("电子钟"));

    //新建定时器对象
    m_timer = new QTimer(this);
    //设置定时器循环触发
    m_timer->setSingleShot(false);  //非单次触发
    //设置定时器触发时间间隔,单位毫秒
    m_timer->setInterval( 1*1000 );

    //关联定时器信号到槽函数
    connect(m_timer, SIGNAL(timeout()), this, SLOT(RecvTimer()));

    //启动定时器
    m_timer->start();

    //调用一次 "回到今天" 槽函数,初始化日历
    on_pushButtonToday_clicked();
}

Widget::~Widget()
{
    delete ui;
}
<QDateTime> 头文件是日期时间类的头文件,也能用该类的静态函数获取当前时间。
在构造函数里,调用了 setWindowTitle() 把主界面窗体标题设置为 "电子钟" ,
然后新建了 QTimer 类对象,定时器对象指针为 m_timer。

定时器有两种工作模式,setSingleShot(false) 代表不是单次触发信号,意味着一直循环触发信号,不会自动停止。
如果参数里是 true,那么定时器触发一次信号就自动停了。我们要做电子钟,当然要一直循环触发。

定时器的信号触发间隔是通过 setInterval 函数设置,参数里的时间以毫秒为单位,我们代码里是 1000 毫秒,
就是设置每隔 1 秒发一次信号,用于更新时钟。

在启动定时器之前,要预先把定时器的信号 timeout() 关联到主窗体的 RecvTimer(),这样定时器启动之后,
每次触发 timeout() 信号,该槽函数都会自动干活。

定时器和信号关联都弄好之后,就可以启动定时器了 m_timer->start() 。
构造函数最后一句是手动调用了 "回到今天" 按钮的槽函数,把日历控件选中的日子设置为今天。

构造函数的准备工作已经做好了,接下来是接收定时器信号的槽函数:
void Widget::RecvTimer()
{
    //获取当前时间
    QDateTime dt = QDateTime::currentDateTime();

    //构造时间字符串
    QString strTime = dt.time().toString("HH:mm:ss");
    //设置 LCD 时钟
    ui->lcdNumber->display(strTime);

    //设置进度条的显示: 秒数%10
    ui->progressBar->setValue( dt.time().second() % 10 );
}
RecvTimer() 槽函数里用 QDateTime::currentDateTime() 获取当前的日期和时间,存到 dt 变量。
对于时间显示,获取时间用 dt.time(),然后用 QTime 对象的 toString() 函数生成当前时间字符串,
时间字符串的格式就是 "HH:mm:ss" ,HH 代表 24 小时格式的小时,mm 代表分钟,ss 代表秒钟,冒号就是真的冒号。
时间字符串刚好 8 个字符,然后将时间字符串交给 lcdNumber 显示即可。
该槽函数最后一句是求出当前时间的秒钟个位数 dt.time().second() % 10 ,然后将秒钟个位数数值设置给进度条,
1 秒钟 就对应进度条的 1 个小格子。

我们在定时器信号对应的槽函数里没有主动更新当前的日期,因为一天时间太长,没必要每秒钟都刷一次日历。
利用下面 "回到今天" 按钮,用户点击一下,就会将日历选中的日子设置为今天了:
void Widget::on_pushButtonToday_clicked()
{
    //获取当前时间
    QDateTime dt = QDateTime::currentDateTime();
    //设置日历为今天
    ui->calendarWidget->setSelectedDate( dt.date() );
    //点击按钮时,输入焦点在按钮上,这时候日历选中的日子是灰色,容易看不清
    //将输入焦点回到日历控件,这样日历选中日子会重新变成高亮蓝色
    ui->calendarWidget->setFocus();
}
在按钮的槽函数里,也是用 QDateTime::currentDateTime() 获取当前日期时间,然后用日历控件的 setSelectedDate() 函数,把选中的日期设置为当前日期。
按钮槽函数里最后一句是把输入焦点强制回到日历控件,因为点击按钮时,输入焦点在按钮上,
日历控件的当前选中日子是灰色的,看不太清楚。
强制把输入焦点返回到日历控件,就能看清选中的日子了。

例子代码不多,就是上面几个函数而已。可以看出用 Qt 实现一个电子时钟是很简单的事,我们生成运行一下例子,可以看到电子时钟的效果:
run
电子钟例子就讲到这,注意 QTimer 是定时器类,而 QTime 是时间类,就差一个字母。

最后补充一点,关于 QTime::toString() 参数里的时间格式串,具体内容请查看 Qt 帮助文档,我们列举几个常用的,上面是 24 小时格式时间的例子:
curTime.toString("HH:mm:ss");
对于 12 小时时间格式,举例如下:
curTime.toString("hh:mm:ss AP");
curTime.toString("hh:mm:ss ap");
小写的 hh 就是十二进制时钟,末尾的 "AP" 代表大写的上午 AM 和下午 PM,小写的 "ap" 代表小写的 am 和 pm。
Qt 比较智能的地方是会根据本地化语言设置上午、下午的文本,所以如果看到类似 "08:17:12 下午" 的字符串不要惊讶。

日期类 QDate 也有类似的 toString() 函数,日期类常用的格式字符串举例如下:
curDate.toString("yyyy-MM-dd");
curDate.toString("yy-MM-dd");
"yyyy" 代表 4 位数年份,"yy" 代表 2 位数年份,大写的 "MM" 是月份(小写的 "mm" 是分钟),日子就是 "dd" 。
curDate.toString("yyyy.MM.dd ddd");
curDate.toString("yyyy.MM.dd dddd");
2 个连续 d 是普通的日子;
而 3 个连续 d 是本地化星期几的缩写,三个字母,如 Mon 是星期一;
4 个连续 d 是本地化的星期几全拼,如 Monday。
对于中文而言,三个及以上的连续 d 都是星期几,没中文缩写。

日期的年、月、日的分隔符可以用横杠、点号、英文逗号、斜杠,或者按自己喜好设置其他分隔符。
本节内容就到这里,下一节介绍内嵌图标、图片资源的程序。



prev
contents
next