[QT_030]Qt学习之显示类的控件(QLabel、QTextBrowser、QGraphicsView、QWebView、QProgressBar、QOpenGLWidget)

本文转自:《Qt编程指南》        作者:奇先生

Qt编程指南,Qt新手教程,Qt Programming Guide

5.5 显示类的控件


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

5.5.1 显示控件概览


在 Qt 设计师左边控件列表里可以看到如下图所示的显示控件:

下面概括介绍图上标出的各个显示控件:
(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 的属性及设置函数、描述如下表所示:
 

属性设置函数描述
digitCountvoid setDigitCount(int)数字的位数限定,这个控件类不限定数值大小,只限定数的位数。
segmentStylevoid setSegmentStyle(SegmentStyle)设置笔画线段的显示风格。SegmentStyle 是枚举类型,有三种,默认是QLCDNumber::Filled
intValue 和
value
void display(int num)
void display(double num)
void display(const QString & s)
既可以获取整型数值 intValue(),也可以获取浮点数值 value()。数值的设置函数不叫 set*,而是 display() 函数。
smallDecimalPointvoid setSmallDecimalPoint(bool)小数点的显示方式,默认情况下该属性为 false,小数点会占一个数字位。如果设置函数参数为 true,小型小数点就显示在数字之间的缝隙,而不会单独占一位。
modevoid 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 常用的属性、设置函数和描述如下表(不常用的略过了,详细情况可以查看帮助文档):
 

属性设置函数描述
orientationvoid setOrientation(Qt::Orientation)显示的方向,Qt::Horizontal 是水平进度条(默认值),Qt::Vertical 是垂直进度条。
formatvoid setFormat(const QString & format)设置显示文本的格式,默认格式是 "%p%",就是百分比显示,如 78%
textVisiblevoid setTextVisible(bool visible)决定是否显示百分比的文本,一般是显示的,例外的是苹果系统风格从不显示进度条文本的。
maximumvoid setMaximum(int maximum)进度条数值的上限。
minimumvoid setMinimum(int minimum)进度条数值的下限。
valuevoid 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)网页浏览视图 QWebEngineView
之前介绍过 QTextBrowser 是一个不完全的浏览器,而 QWebEngineView则是一个完整的浏览器控件,支持 HTML 全部特性,当然也有网络访问功能。QWebView 位于 Qt webenginewwidgets

模块里,使用该控件需要添加该模块到 *.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 文件,进入设计模式,按照下图拖入控件:

首先把主界面窗体大小设置为 480*400,这样能容纳较大的标签控件。
界面里第一行是一个标签控件,objectName 为 labelShow,文本内容清空,因为是用来显示图片用的。
标签控件占用矩形 geometry 为 X:10,Y:10,宽度:461,高度:321,占据着窗体最大的一块地皮。
之前提到要用滚动区域显示大图,现在 labelShow 的矩形是为后面代码里的滚动区域对象占据的,利用滚动区域显示大图。

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

主界面就是如图上设置,现在为四个按钮都添加 clicked() 信号对应的槽函数:

添加四个按钮的槽函数之后,保存界面文件,回到代码编辑模式,添加例子需要的代码。
首先是 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 等动态图。下面生成并运行例子,看看效果。例子用到的图片可以从教程代码文件夹的网址下载:
imgshow
两张图,opensuse.png 和 cat.gif,放到例子代码文件夹一块。

运行例子后,可以看到带有滚动区域的效果:

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

点击 "打开图片" 按钮,打开本节的 opensuse.png ,由于这张图比较大,所以需要滚动浏览的图片区域也多:

如果有读者记性比较好,5.3.4 节 simplebrowser 例子文件夹有一张 opensuse.jpg,读者可尝试打开旧的 opensuse.jpg 图片,会出现如下错误:

都是图片,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 动态图,先看到初始帧:

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

5.5.4 电子钟示例


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

重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
①项目名称 timeshow,创建路径 D:\QtProjects\ch05,点击下一步;
②套件选择里面选择全部套件,点击下一步;
③基类选择 QWidget,点击下一步;
④项目管理不修改,点击完成。
建好项目之后,打开窗体 widget.ui 文件,进入设计模式,按照下图拖入控件:

首先把主窗体大小调整为 400*300,方便容纳内部的控件。
窗体内部有两行控件,第一行的是 LCD 数字显示控件和进度条控件,
LCD 数字显示控件的对象名 objectName 为 lcdNumber,占据的矩形为
X:10,Y:10,宽度:341,高度:95,其他属性如下图所示:

显示位数 digitCount 为 8,其中小时占 2 位数,分钟占 2 位数,秒钟占 2 位数,还有 2 个冒号分隔。
segmentStyle 是选择扁平风格 flat,这种笔画最清晰。数值设置为 8 个 8 。

进度条控件的对象名 objectName 为 progressBar,占位矩形为
X:360,Y:10,宽度:31,高度:95,其他属性如下图设置:

最小值是 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 属性编辑框右边的 "..." 小按钮编辑按钮的文本如下图所示:

把按钮宽度设置比较窄,文本一个字占一行,那么看起来就像是一个垂直按钮了。

界面就是如上设置,因为图中有三个控件纯粹作为显示用的,不需要关联 LCD 数字显示、进度条、日历的信号,
只需要为 "回到今天" 按钮添加一个普通的 clicked() 信号对应的槽函数即可:

添加槽函数之后,保存界面文件,回到代码编辑模式,下面开始添加例子的代码。

编辑 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 实现一个电子时钟是很简单的事,我们生成运行一下例子,可以看到电子时钟的效果:

电子钟例子就讲到这,注意 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 都是星期几,没中文缩写。
日期的年、月、日的分隔符可以用横杠、点号、英文逗号、斜杠,或者按自己喜好设置其他分隔符。

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值