目录
3.0 本章导论
本章开始正式接触Qt的窗口部件。第2章曾看到Qt Creator提供的默认基类只有QMain Window、QWidget和QDialog这3种。这3种窗体也是以后用得最多的。
QMain Window是带有菜单栏和工具栏的主窗口类。
QDialog是各种对话框的基类,而它们全部继承自QWidget。
不仅如此,其实所有的窗口部件都继承自QWidget,如下图所示。这一章会讲解QWidget、QDialog和一些其他常用部件类,而QMain-Window将在第5章讲解。本章内容可以在帮助中索引QWidget关键字查看。
![]()
图3-1 QWidget类关系图
3.1 基础窗口部件 QWidget
QWidget类是所有用户界面对象的基类,被称为基础窗口部件。在图3-1中可以看到,QWidget继承自QObject类和QPaintDevice类。其中,QObject类是所有支持Qt对象模型(Qt Object Model)的对象的基类,QPaintDevice类是所有可以绘制的对象的基类。
这一节先讲解Qt窗口部件的概念和窗口类型,然后再讲解Qt窗口的几何布局,最后讲解Qt程序调试方面的内容。
3.1.1 窗口、子部件以及窗口类型
1.窗口与子部件
先来看一个例子。打开Qt Creator,新建空的qmake项目,项目名称为mywidget1,完成后再mywidget1.pro中添加“QT += widgets”。然后往项目中添加main.cpp,并添加如下代码:
#include <QtWidgets> int main(int argc, char *argv[]) { QApplication a(argc, argv); // 新建QWidget类对象,默认parent参数是0,所以它是个窗口 QWidget *widget = new QWidget(); // 设置窗口标题 widget->setWindowTitle(QObject::tr("我是widget")); // 新建QLabel对象,默认parent参数是0,所以它是个窗口 QLabel *label = new QLabel(); label->setWindowTitle(QObject::tr("我是label")); // 设置要显示的信息 label->setText(QObject::tr("label:我是个窗口")); // 改变部件大小,以便能显示出完整的内容 label->resize(180, 20); // label2指定了父窗口为widget,所以不是窗口 QLabel *label2 = new QLabel(widget); label2->setText(QObject::tr("label2:我不是独立窗口,只是widget的子部件")); label2->resize(250, 20); // 在屏幕上显示出来 label->show(); widget->show(); int ret = a.exec(); delete label; delete widget; return ret; }
这里包含了头文件#include <QtWidgets>,因为下面所有要用到的类,如QAp-plication、QWidget等,都包含在QtWidgets模块中,为了简便,只包含了QtWidgets的头文件。
需要说明,一般的原则是要包含尽可能少的头文件,这里直接包含整个模块,只是为了让读者知道这样用也可以。程序中定义了一个 QWidget类对象的指针widget和两个QLabel对象指针label与label2,其中 label 没有父窗口,而label2在 widget中,widget是其父窗口。
注意:这里使用new操作符为label2分配了空间,但是并没有使用delete进行释放,这是因为在Qt中销毁父对象的时候会自动销毁子对象,这里 label2指定了parent为widget,所以在delete widget时会自动销毁作为widget子对象的 label2。第7章对象树部分会对这个问题进行详细讲解。然后运行程序,效果如图3-2所示。
窗口部件(Widget)简称部件,是Qt中建立用户界面的主要元素。像主窗口、对话框、标签、还有以后要介绍到的按钮、文本输人框等都是窗口部件。这些部件可以接收用户输入,显示数据和状态信息,并且在屏幕上绘制自己。有些也可以作为一个容器来放置其他部件。Qt中把没有嵌入到其他部件中的部件称为窗口,一般窗口都有边框和标题栏,就像程序中的widget和label一样。QMain Window和大量的QDialog子类是最一般的窗口类型。窗口就是没有父部件的部件,所以又称为顶级部件(top-level widget)。与其相对的是非窗口部件,又称为子部件(child widget)。在Qt中大部分部件被用作子部件,嵌入在别的窗口中,如程序中的 label2。这部分内容可以查看关键字Qt Widgets和 Window and Dialog Widgets。
QWidget提供了自我绘制和处理用户输入等基本功能,Qt提供的所有界面元素不是QWidget 的子类就是与QWidget的子类相关联。要设计自己的窗口部件,可以继承自QWidget或者是它的子类。
2.窗口类型
前面讲到窗口一般都有边框和标题栏,其实这也不是必需的。
QWidget的构造函数有两个参数:QWidget * parent = 0 和Qt ::WindowFlags f =0,前面的parent指父窗口部件,默认值为0,表明没有父窗口;而后面的f参数是Qt::WindowFlags类型的,是Qt :: WindowType枚举类型值的或组合。Qt:: WindowType枚举类型用来为部件指定各种窗口系统属性,比如f=0表明窗口类型的值为Qt : : Widget,这是QWidget的默认类型,这种类型的部件如果有父窗口,那么它就是子部件,否则就是独立窗口。Qt::WindowType包括了很多类型,下面演示其中的Qt::Dialog 和Qt::SplashScreen,更改程序中新建对象的那两行代码:
QWidget *widget = new QWidget(0, Qt::Dialog); QLabel *label = new QLabel(0, Qt::SplashScreen);
运行程序的效果如下图所示。
可以看到,更改窗口类型后窗口的样式发生了改变,一个是对话框类型,一个是欢迎窗口类型。而窗口标志Qt :: WindowFlags可以是多个窗口类型枚举值进行位或操作,再次更改代码: QWidget *widget = new QWidget(0, Qt::Dialog|Qt::FramelessWindowHint); QLabel *label = new QLabel(0, Qt::SplashScreen|Qt::WindowStaysOnTopHint);
Qt ::Frameless WindowHint用来产生一个没有边框的窗口,而Qt:: WindowStaysOnTopHint用来使该窗口停留在所有其他窗口上面,如下图所示。虽然单击了Qt Creator,但只有 widget窗口隐藏到了后面,label窗口依然在最上面。
这里只列举了两个简单的例子来演示f参数的使用,如果还想看其他值的效果,则可以在帮助中索引 Qt :: WindowFlags关键字,然后自己更改代码。在Qt的示例程序中有一个 Window Flags程序演示了所有窗口类型,可以直接在Qt Creator的欢迎模式中打开它。
QWidget中还有一个set WindowState()函数用来设置窗口的状态,其参数由Qt : : WindowStates 指定,是Qt::WindowState枚举类型值的或组合。窗口状态Qt :: WindowState包括最大化Qt:: WindowMaximized、最小化Qt:: Win-dowMinimized,全屏显示Qt : : WindowFullScreen和活动窗口Qt : : WindowActive等,默认值为正常状态Qt : : WindowNoState。
3.1.2 窗口几何布局
对于一个窗口,往往要设置它的大小和运行时出现的位置,这就是本小节要说的窗口几何布局。在前面的例子中已经看到了, widget 默认的大小就是它所包含的子部件label2的大小,而 widget和 label出现时在窗口上的位置也是不确定的。对于窗口的大小和位置,根据是否包含边框和标题栏两种情况,要用不同的函数来获取。可以在帮助索引中查看 Window and Dialog Widgets关键字,文档中显示了窗口的几何布局图,如下图所示。
这里的函数分为两类,一类是包含框架的,一类是不包含框架的:
包含框架:x()、y()、frameGeometry().pos()、move()等函数
不包含框架:geometry( )、 width() 、height() 、rect()、 size()等函数。
3.1.3 程序调试
下面会在讲解窗口几何布局的几个函数的同时,讲解程序调试方面的内容。这部分内容可以在帮助索引中通过Interacting with the Debugger和 Debugging a C++Example Application关键字查看。
1.设置断点
在前面程序的基础上进行更改,将主函数内容更改如下:
#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget widget; int x = widget.x(); int y = widget.y(); QRect geometry = widget.geometry(); QRect frame = widget.frameGeometry(); return a.exec(); }
开始调试程序之前可以先看一下这些函数的介绍。首先将光标定位到函数上,然后按下F1键,打开函数的帮助文档。可以看到,x()、y()分别返回部件在其父部件中位置坐标的x、y值,它们的默认值为0。
而 geometry()和frameGeometry()函数分别返回没有边框和包含边框的窗口框架矩形的值,其返回值是QRect类型的,就是一个矩形,它的形式是(位置坐标,大小信息),也就是(x,y,宽,高)。
下面在“int x = widget.x();”一行代码的标号前面单击来设置断点。所谓断点,就是程序运行到该行代码时会暂停下来,从而可以查看一些信息。要取消断点,只要在那个断点上再单击一下就可以了。设置好断点后便可以按下F5或者左下角的调试按钮开始调试。这时程序会先进行构建再进入调试模式,这个过程可能需要一些时间。程序构建时可能会出现警告,那是因为这里定义了变量却没有使用造成的。进入调试模式后的效果如下图所示。
下面对调试模式的几个按钮和窗口进行简单介绍:
①继续按钮。程序在断点处停了下来,按下继续按钮后,程序便会像正常运行一样,执行后面的代码,直到遇到下一个断点,或者程序结束。
②停止调试按钮。按下该按钮后结束调试。
③单步跳过按钮。直接执行本行代码,然后指向下一行代码。④单步进入按钮。进入调用的函数内部。
⑤单步跳出按钮。当进入函数内部时,跳出该函数,一般与单步进入配合使用。⑥重新启动调试会话。
⑦显示源码对应的汇编指令,并可以单步调试。
⑧堆栈视图。这里显示了从程序开始到断点处,所有嵌套调用的函数所在的源文件名和行号。
⑨其他视图。这里可以选择多种视图,主要有局部变量和表达式视图(locals andExpressions),用来显示局部变量和它们的类型及数值;断点视图(Breakpoints)用来显示所有的断点,以及添加或者删除断点;线程视图(Threads)用来显示所有的线程和现在所在的线程;快照视图(Snapshots)用来管理快照,快照可以保存当前的调试状态。
2.单步调试
先单击“单步进入”按钮,或者按下F11,则堆栈视图中显示x()函数在源码中的位置,下面直接单击“单步跳出”按钮回到原来的断点处。然后一直单击“单步跳过”按钮,单步执行程序并查看局部变量和表达式视图中相应变量值的变化情况。
![]()
单步进入 ![]()
单步进入 进入x的声明中 ![]()
单步跳出,回到原来的断点处
执行到最后一行代码"return a, exec();”时,单击“停止调试”按钮结束调试。
补充说明一下,在程序调试过程中可以进入到Qt类的源码中,其实还有一个很简单的方法也可以实现这个功能,就是在编辑器中将鼠标光标定位到一个类名或者函数上,然后按下F2键,或者右击,选择“跟踪光标位置的符号”,则编辑器就会跳转到其源码处。
从变量监视器中可以看到,x,y ,geometry和frame这4个变量初始值都是一个随机未知数。等到调试完成后,x、y的值均为0,这是它们的默认值。而 geometry的值为640×480+0+0,frame的值为639×479+0+0。现在对这些值还不是很清楚,不过,为什么x、y的值会是0呢?读者可能会想到,应该是窗口没有显示的原因,那么下面更改代码,让窗口先显示出来再看这些值。在“QWidget widget;”一行代码后添加一行代码:widget.show();
#include <QApplication> #include <QWidget> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget widget; widget.show(); int x = widget.x(); int y = widget.y(); QRect geometry = widget.geometry(); QRect frame = widget.frameGeometry(); return a.exec(); }
再次调试程序,这时会发现窗口只显示了一个标题栏,先不管它。
![]()
继续在Qt Creator中单击“单步跳过”按钮。将程序运行到最后一行代码“return a. exec();”时再次单击“单步跳过”按钮后,程序窗口终于显示出来了。这是因为只有程序进入主事件循环后才能接收事件,而show()函数会触发显示事件,所以只有在完成a. exec()函数调用进入消息循环后才能正常显示。这次看到几个变量的值都有了变化,但是这时还是不清楚这些值的含义。
因为使用调试器进行调试要等待一段时间,而且步骤很麻烦,对于初学者来说,如果按错了按钮,还很容易出错。下面将介绍一个更简单的调试方法。
3.使用 qDebug 函数
程序调试过程中常用的是qDebug()函数,它可以将调试信息直接输出到控制台,当然,Qt Creator中是输出到应用程序输出栏。下面更改上面的程序:
#include <QApplication> #include <QWidget> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWidget widget; widget.resize(400, 300); // 设置窗口大小 widget.move(200, 100); // 设置窗口位置 widget.show(); int x = widget.x(); qDebug("x: %d", x); // 输出x的值 int y = widget.y(); qDebug("y: %d", y); QRect geometry = widget.geometry(); QRect frame = widget.frameGeometry(); qDebug() << "geometry: " << geometry << "frame: " << frame; return a.exec(); }
要使用qDebug()函数,就要添加#include <QDebug>头文件。这里使用了两种输出方法:
一种是直接将字符串当作参数传给qDebug()函数,例如上面使用这种方法输出x和y的值。
另一种方法是使用输出流的方式一次输出多个值,它们的类型可以不同,如程序中输出 geometry和 frame的值。
需要说明的是,如果只使用第一种方法,就不需要添加<QDebug>头文件;如果使用第二种方法就必须添加该头文件。因为第一种方法很麻烦,所以经常使用第二种方法。程序中还添加了设置窗口大小和位置的代码。下面运行程序,在应用程序输出窗口可以看到输出信息,如下图所示:
从输出信息中可以清楚地看到几个函数的含义。使用qDebug()函数的第二种方法时还可以让输出自动换行,下面来看一下其他几个函数的用法。在“return a.exec(0);”一行代码前添加如下代码:
qDebug() << "pos :" << widget.pos() << endl << "rect = " << widget.rect() << endl << "size = " << widget.size() << endl << "width:" << widget.width() << endl << "height :" << widget.height();
endl是起换行作用的。根据程序的输出结果可以很明了地看到这些函数的作用。
其中,pos()函数返回窗口的位置,是一个坐标值,上面的x()、 y()函数返回的就是它的x,y坐标值;
rect()函数返回不包含边框的窗口内部矩形,窗口内部左上角是(0,0)点;
size()函数返回不包含边框的窗口大小信息;
width()和 height()函数分别返回窗口内部的宽和高。从数据可以看到,前面使用的调整窗口大小的resize()函数是设置的不包含边框的窗口大小。
到这里,QWidget的内容就告一段落,其他特性会在后面的章节中涉及。
3.2 对话框 QDialog
这一节主要讲述对话框类,先从对话框的介绍讲起,然后讲述两种不同类型的对话框,再讲解一个由多个窗口组成并且窗口间可以相互切换的程序,最后介绍Qt提供的几个标准对话框。讲解的过程中还会涉及信号和槽的初步知识。本节的内容可以在帮助索引中通过QDialog和 Dialog Windows关键字查看。
3.2.1 模态和非模态对话框
1.变量作用域影响窗口存在时长
QDialog类是所有对话框窗口类的基类。对话框窗口是一个经常用来完成短小任务或者和用户进行简单交互的顶层窗口。按照运行对话框时是否还可以和该程序的其他窗口进行交互,对话框常被分为两类:模态的(modal)和非模态的(modeless)。关于这两个概念,下面先看一个例子。
新建Qt Widgets应用,项目名称为mydialog1,基类选择QWidget,类名为MyWidget,然后在mywidget.cpp文件中添加以下代码:#include "mywidget.h" #include "ui_mywidget.h" #include <QDialog> MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); QDialog dialog(this); dialog.show(); } MyWidget::~MyWidget() { delete ui; }
在 MyWidget类的构造函数中定义了一个QDialog类对象,还指定了dialog 的父窗口为MyWidget类对象(即this参数的作用),最后调用show()函数让其显示。运行程序时就发现,一个窗口一闪而过,然后就只显示 MyWidget窗口了,为什么会这样呢?因为对于一个函数中定义的变量,等这个函数执行结束后,它就会自动释放。也就是说,这里的dialog对象只在这个构造函数中有用,等这个构造函数执行完了,dialog 也就消失了。为了不让 dialog消失,可以将QDialog 对象的创建代码更改如下:
QDialog * dialog = new QDialog(this); dialog->show();
使用了QDialog对象的指针,并使用new运算符开辟了内存空间,再运行程序就可以正常显示了。需要说明的是,我们说定义一个对象是指“QDialog dialog;”这样的方式,而像“QDialog * dialog;”这样不能被称为定义了一个对象,而应该说成定义了一个指向QDialog类对象的指针变量。后面也会把“QDialog * dialog;”说成是定义了一个QDialog对象。再补充一点,这里为dialog对象指明了父窗口,所以就没有必要使用delete来释放该对象了,原因会在第7章详细讲解。
其实,不用指针也可以让对话框显示出来,可以将创建代码更改如下:QDialog dialog(this); dialog.exec();
这时运行程序就会发现对话框弹出来了,但是 MyWidget窗口并没有出来,当关闭对话框后,MyWidget窗口才弹出来。这个对话框与前面那个对话框的效果不同,称它为模态对话框,而前面那种对话框称为非模态对话框。
2.模态对话框与非模态对话框
模态对话框就是在没有关闭它之前,不能再与同一个应用程序的其他窗口进行交互,比如新建项目时弹出的对话框。而对于非模态对话框,既可以与它交互,也可以与同一程序中的其他窗口交互,如 Microsoft Word中的查找替换对话框。
就像前面看到的,要想使一个对话框成为模态对话框,则只需要调用它的exec()函数;而要使其成为非模态对话框,则可以使用new操作来创建,然后使用show()函数来显示。其实使用show()函数也可以建立模态对话框,只须在其前面使用setModal()函数即可
例如:
QDialog * dialog = new QDialog(this); dialog -> setModal(true); dialog -> show();
运行程序后可以看到,生成的对话框是模态的。但是,它与用exec()函数时的效果是不一样的,因为现在的MyWidget窗口也显示出来了。这是因为调用完show()函数后会立即将控制权交给调用者,程序可以继续往下执行。
![]()
只能先处理左面那个,否则右面点击无反应 而调用exec()函数却不同,只有当对话框被关闭时才会返回。与setModal()函数相似的还有一个setWindow-Modality()函数,它有一个参数来设置模态对话框要阻塞的窗口类型,可以是Qt : :NonModal(不阻塞任何窗口,就是非模态),Qt:: WindowModal(阻塞它的父窗口,所有祖先窗口以及它们的子窗口)或Qt::ApplicationModal(阻塞整个应用程序的所有窗口)。而setModal()函数默认设置的是Qt: :ApplicationModal。
3.2.2 多窗口切换
1.认识信号与槽
Qt中使用信号和槽机制来完成对象之间的协同操作。简单来说,信号和槽都是函数,比如单击窗口上的一个按钮后想要弹出一个对话框,那么可以将这个按钮的单击信号和自定义的槽关联起来,在这个槽中创建一个对话框并且显示它。这样,单击这个按钮时就会发射信号,进而执行槽来显示一个对话框。下面来看一个例子:
双击mywidget.ui文件,在设计模式中往界面添加一个Label和一个Push Button,在属性栏中将Push Button的objectName改为showChildButton,然后更改Label 的显示文本为“我是主界面!”,更改按钮的显示文本为“显示子窗口”。然后回到编辑模式打开mywidget.h文件,在MyWidget类声明的最后添加槽的声明:
#ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> QT_BEGIN_NAMESPACE namespace Ui { class MyWidget; } QT_END_NAMESPACE class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = nullptr); ~MyWidget(); private: Ui::MyWidget *ui; public slots: void showChildDialog(); }; #endif // MYWIDGET_H
这里自定义了一个槽,槽一般使用slots关键字进行修饰(Qt 4中必须使用,Qt 5使用新connect语法时可以不用,为了与一般函数进行区别,这里建议使用),这里使用了public slots,表明这个槽可以在类外被调用。
现在到源文件中编写这个槽的实现代码,Qt Creator设计了一个快速添加定义的方法:单击showChildDialog()槽,同时按下Alt+Enter键(也可以在函数上右击,在弹出的级联菜单中选择Refactor菜单项),会弹出如下图所示的“在mywidget.cpp添加定义”选项,再次按下回车键 Enter,编辑器便会转到mywidget.cpp文件中,并且自动创建showChildDialog()槽的定义,只需要在其中添加代码即可。这种方法也适用于先在源文件中添加定义,然后自动在头文件中添加声明的情况。
void MyWidget::showChildDialog() { QDialog * dialog = new QDialog(this); dialog -> show(); }
这里新建了对话框并让其显示,然后再更改MyWidget类的构造函数如下:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MyWidget) { ui->setupUi(this); connect(ui->showChildButton, &QPushButton::clicked, this, &MyWidget::showChildDialog); }
这里使用了connect()函数将按钮的单击信号clicked()与新建的槽进行关联。
clicked()信号在 QPushButton类中定义,而 connect()是 QObject类中的函数,因为MyWidget类继承自QObject,所以可以直接使用它。这个函数中的4个参数分别是发射信号的对象、发射的信号、接收信号的对象和要执行的槽。运行程序,然后单击主界面上的按钮就会弹出一个对话框。
其实,对于信号和槽的关联还有一种方法,叫自动关联。
前面那种方法叫手动关联。自动关联就是将关联函数整合到槽命名中,比如前面的槽可以重命名为on_showChildButton_clicked() ,就是由字符on ,发射信号的部件对象名和信号名组成。这样就可以去掉connect()关联函数了,具体做法在下面介绍。
打开mywidget.cpp文件。#ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> namespace Ui { class MyWidget; } class MyWidget : public QWidget { Q_OBJECT public: explicit MyWidget(QWidget *parent = 0); ~MyWidget(); private: Ui::MyWidget *ui; public slots: void on_showChildButton_clicked(); }; #endif // MYWIDGET_H
#include "mywidget.h" #include "ui_mywidget.h" #include <QDialog> MyWidget::MyWidget(QWidget *parent) : QWidget(parent), ui(new Ui::MyWidget) { ui->setupUi(this); } MyWidget::~MyWidget() { delete ui; } void MyWidget::on_showChildButton_clicked() { QDialog *dialog = new QDialog(this); dialog->show(); }
在MyWidget类的构造函数中删除connect()函数,然后更改showChildDialog()槽的名字,Qt Creator中提供了一个快捷方式来更改所有该函数出现的地方,从而不再需要逐一更改函数名。先在 showChildDialog上右击,在弹出的级联菜单中选择Refactor→Rename Symbol Under Cursor,或者直接使用Ctrl+Shift+R快捷键,在出现的替换栏中输入 on_showChildButton_clicked,再单击Replace就可以了。这时源文件和头文件中相应的函数名都进行了更改。现在运行程序,和前面的效果是一样的。
对于这两种关联方式,后一种形式很简便,用Qt设计器直接生成的槽就是使用这种方式。不过,对于不是在Qt设计器中往界面上添加的部件,就要在调用setupUi()函数前定义该部件,而且还要使用setObjectName()函数指定部件的对象名,这样才可以使用自动关联。在编写程序时一般都使用第一种connect方式。信号和槽在第7章还会深入讲解。
2.自定义对话框
关于自定义对话框,其实在前面的第一个helloworld程序中就已经实现了。这里再自定义一个对话框,给它添加按钮,并在Qt设计器中设计信号和槽,然后实现与主界面的切换。其项目源码路径,步骤如下:
第一步,添加自定义对话类框。依然在前面的项目中更改。首先向该项目中添加Qt设计师界面类。界面模板选择Dialog without Buttons,类名改为MyDialog。然后在设计模式中向窗口添加两个Push Button,并且分别更改其显示文本为“进人主界面”和“退出程序”。
第二步,设计信号和槽。这里使用设计器来实现“退出程序”按钮的关联。单击设计器上方的Edit Signals/ Slots图标,或者按下快捷键F4,于是进入了部件的信号和槽的编辑模式。
在“退出程序”按钮上按住鼠标左键,然后拖动到窗口界面上,这时松开鼠标左键。在弹出的配置连接对话框中选择“显示从 QWidget继承的信号和槽”选项,然后在左边的QPushButton栏中选择信号clicked(),在右边的QDialog栏中选择对应的槽close(),完成后单击OK按钮。
(这里还可以单击“编辑”按钮添加自定义的槽,不过这还需要在MyDialog 类中实现该槽)。这时“退出程序”按钮的单击信号就和对话框中的关闭操作槽进行了关联。
要想取消这个关联,只须在信号和槽编辑模式中选择这个关联;当它变为红色时,按下Delete键,或者右击选择“删除”。
也可以在设计器下方的信号和槽编辑器中看到设置好的关联。
当然,直接在信号和槽编辑器中建立关联也是可以的,它与鼠标选择部件进行关联是等效的。设置好关联后按下F3键,或者单击“编辑控件”图标,则回到部件编辑模式。关于设计器中信号和槽的详细使用,可以在帮助索引中通过Qt Designer's Signals and Slots Editing Mode关键字查看。
现在设置“进入主界面”按钮的信号和槽的关联。在该按钮上右击,在弹出的级联菜单中选择“转到槽”,然后在弹出的对话框中选择clicked()信号,并单击OK按钮。这时便会进人代码编辑模式,并且定位到自动生成的 on_pushButton_clicked()槽中,在其中添加代码:
void MyDialog::on_pushButton_clicked() { accept(); }
这个accept()函数是QDialog类中的一个槽,对于一个使用exec()函数实现的模态对话框,执行了这个槽就会隐藏这个模态对话框,并返回QDialog : :Accepted 值,这里就是要使用这个值来判断是哪个按钮被按下了。与其对应的还有一个 reject()槽,它可以返回一个QDialog :: Rejected值,前面的“退出程序”按钮也可以关联这个槽。
前面讲述了两种关联信号和槽的方法,第一种是直接在设计器中进行,这个更适合在设计器中的部件间进行。第二种方法是在设计器中直接进人相关信号的槽,这个与前面讲到的手写函数是一样的,它用的就是自动关联,这样也会在.h文件中自动添加该槽的声明,我们只须更改其实现代码就可以了。在以后的章节中,如果在设计器中添加的部件要使用信号和槽,那么都会使用第二种方法。
3.在主界面中使用自定义的对话框
更改main.cpp的代码:
#include "mywidget.h" #include <QApplication> #include "mydialog.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MyWidget w; MyDialog dialog; //判断diag执行结果 if(dialog.exec() == QDialog::Accepted) { //如果按下了进入主页面按钮显示主页面 w.show(); return a.exec(); } else return 0; }
主函数中建立 MyDialog对象,然后判断其exec()函数的返回值,如果按下了“进人主界面”按钮,返回值应该是QDialog : : Accepted,则显示主界面,并且正常执行程序;如果不是,则直接退出程序。
运行程序后可以发现,已经实现了从登录对话框到主界面,再从主界面显示一个对话框的应用了。
再来实现可以从主界面重新进入登录界面的功能。双击mywidget.ui文件,在设计模式中再向界面添加两个Push Button,分别更改它们的显示文本为“重新登录”和“退出”。然后使用信号和槽模式将“退出”按钮的clicked()信号和 MyWidget界面的close()槽关联。
完成后再转到“重新登录”按钮的clicked()信号的槽,并更改如下:
void MyWidget::on_pushButton_clicked() { //先关闭主界面,其实是隐藏起来了,并没有真正退出。然后新建Mydialog对象 close(); MyDialog dlg; //如果按下进入主窗口按钮再次显示主页面,否则直接退出 if(dlg.exec()==QDialog::Accepted) show(); }
需要说明的是那个close()槽,它不一定使程序退出,只有当只剩下最后一个主界面了(就是没有父窗口的界面),这时调用close()槽,程序才会退出;而其他情况下界面只是隐藏起来了,并没有被销毁。这里还需要包含MyDialog类的头文件#include“mydialog.h”,然后运行程序查看效果。
3.2.3 标准对话框
Qt提供了一些常用的对话框类型,它们全部继承自QDialog类,并增加了自己的特色功能,比如获取颜色、显示特定信息等。下面简单讲解这些对话框,可以在帮助索引中查看Standard Dialogs 关键字,也可以直接索引相关类的类名。
这里新建一个项目,即新建Qt Widgets应用,项目名称为mydialog2,基类选择QWidget,类名改为MyWidget。完成后双击mywidget.ui文件进入设计模式,在界面上添加一些按钮,如下图所示。
1.颜色对话框
颜色对话框类QColorDialog提供了一个可以获取指定颜色的对话框部件。
下面创建一个颜色对话框。先在 mywidget.cpp文件中添加#include <QDebug>和#include <QColorDialog>头文件,然后从设计模式进入“颜色对话框”按钮的clicked()单击信号槽。更改如下:
void MyWidget::on_pushButton_clicked() { QColor color = QColorDialog::getColor(Qt::red,this,tr("颜色对话框")); qDebug() << "color" << color; }
这里使用了QColorDialog的静态函数 getColor()来获取颜色,它的3个参数的作用分别是:设置初始颜色,指定父窗口和设置对话框标题。这里的Qt : red是Qt预定义的颜色对象,可以直接单击该字符串,然后按下F1查看其快捷帮助,或者在帮助索引中通过Qt: :GlobalColor关键字,从而查看到所有的预定义颜色列表。
getColor()函数返回一个QColor类型数据。现在运行程序,然后单击“颜色对话框”按钮,如果不选择颜色,直接单击OK,那么输出信息应该是QColor(ARGB 1,1,0,0),这里的4个数值分别代表透明度(alpha),红色(red),绿色(green)和蓝色(blue)。它们的数值都是从0.0~1.0,有效数字为6位。对于alpha来说,1.0表示完全不透明,这是默认值,而0.0表示完全透明。对于三基色红,绿、蓝的数值,还可以使用0~255来表示,颜色对话框中就是使用这种方法。其中,0表示颜色最浅,255表示颜色最深。在0~255与0.0~1.0之间可以通过简单的数学运算来对应,其中0对应0.0,255对应1.0。
在颜色对话框中还可以添加对alpha的设置,就是在getColor()函数中再使用最后一个参数:
这里的QColorDialog : :ShowAlphaChannel 用来显示 alpha设置。可以运行程序查看效果。void MyWidget::on_pushButton_clicked() { QColor color = QColorDialog::getColor(Qt::red,this,tr("颜色对话框"),QColorDialog::ShowAlphaChannel); qDebug() << "color" << color; }
前面使用了QColorDialog类的静态函数来直接显示颜色对话框,好处是不用创建对象。但是如果想要更灵活的设置,则可以先创建对象,然后进行各项设置:void MyWidget::on_pushButton_clicked() { //创建对象 QColorDialog dialog(Qt::red,this); //显示alpha选项 dialog.setOption(QColorDialog::ShowAlphaChannel); //以模态方式运行对话框 dialog.exec(); //获取当前颜色 QColor color = dialog.currentColor(); //输出颜色信息 qDebug() << "color:" << color; }
这样的代码与前面的实现效果是等效的。
2.文件对话框
文件对话框QFileDialog类提供了一个允许用户选择文件或文件夹的对话框。
继续在mywidget.cpp中添加#include<QFileDialog>头文件,然后从设计模式转到“文件对话框”按钮的单击信号槽,并更改如下:
void MyWidget::on_pushButton_5_clicked() { QString fileName = QFileDialog::getOpenFileName(this,tr("文件对话框"),"D:",tr("图片文件(*png *jpg)")); qDebug() << "fileName:" << fileName; }
这里使用了QFileDialog类中的getOpenFileName()函数来获取选择的文件名,这个函数会以模态方式运行一个文件对话框。
打开后选择一个文件,单击“打开”按钮后,这个函数便可以返回选择的文件的文件名。它的4个参数的作用分别是:指定父窗口、设置对话框标题、指定默认打开的目录路径和设置文件类型过滤器。
如果不指定文件过滤器,则默认选择所有类型的文件。这里指定了只选择png和jpg两种格式的图片文件(注意,代码中*png和*jpg之间需要一个空格),那么在打开的文件对话框中只能显示目录下这两种格式的文件。还可以设置多个不同类别的过滤器,不同类别间使用两个分号“;;”隔开,例如,添加文本文件类型:
void MyWidget::on_pushButton_5_clicked() { QString fileName = QFileDialog::getOpenFileName(this,tr("文件对话框"),"R:",tr("图片文件(*png *jpg);;文本文件*(*txt)")); qDebug() << "fileName:" << fileName; }
这时再次运行程序,就可以在文件对话框的文件类型中选择“文本文件”类型了前面这个程序只能选择单个文件,要同时选择多个文件,则可以使用getOpenFileNames()函数,例如:
void MyWidget::on_pushButton_5_clicked() { QStringList fileName = QFileDialog::getOpenFileNames(this,tr("文件对话框"),"R:",tr("图片文件(*png *jpg)")); qDebug() << "fileName:" << fileName; }
运行程序就可以同时选择多个图片文件了,多个文件名存放在QStringList类型变量中。当然也可以不使用这些静态函数,而是建立对话框对象来操作。除了上面的两个函数外,QFileDialog类还提供了getSaveFileName()函数来实现保存文件对话框和文件另存为对话框,还有getExistingDirectory()函数来获取一个已存在的文件夹路径。因为它们的用法与上面的例子类似,这里就不再举例。
3.字体对话框
字体对话框QFontDialog类提供了一个可以选择字体的对话框部件。先添加#include <QFontDialog>头文件,然后转到“字体对话框”按钮的单击信号槽,更改如下:
void MyWidget::on_pushButton_2_clicked() { //ok用于标记是否单击了ok按钮 bool ok; QFont font = QFontDialog::getFont(&ok,this); //如果单机了ok,那么让字体对话框按钮使用新字体 //如果点击cancel那么输出信息 if(ok) { ui -> pushButton_2 -> setFont(font); } else { qDebug() << tr("没有选择新字体"); } }
这里使用了QFileDialog类的getFont()静态函数来获取选择的字体。这个函数的第一个参数是bool类型变量,用来存放按下的按钮状态,比如在打开的字体对话框中单击了OK按钮,那么这里的ok就为true,这样来告诉程序已经选择了字体。
测试1:按按钮
测试2:没按
4.输入对话框
输入对话框QInputDialog类用来提供一个对话框,可以让用户输入一个单一的数值或字符串。先添加头文件#include <QInputDialog>,然后进入“输入对话框”按钮的单击信号槽,更改如下:
void MyWidget::on_pushButton_6_clicked() { bool ok; //获取字符串 QString string = QInputDialog::getText(this,tr("输入字符串对话框"),tr("请输入用户名"),QLineEdit::Normal,tr("admin"),&ok); if(ok) { qDebug() << "string" << string; } //获取整数 int value1 = QInputDialog::getInt(this,tr("输入整数对话框"),tr("请输入-1000-1000的整数"),100,-1000,1000,10,&ok); if(ok) { qDebug() << "value1" << value1; } //获取浮点数 double value2 = QInputDialog::getDouble(this,tr("输入浮点数对话框"),tr("请输入-1000-1000之间的数值"),0.00,-1000,1000,10,&ok); if(ok) { qDebug() << "value2:" << value2; } //获取条目 QStringList items; items << tr("条目1") << tr("条目2"); QString item = QInputDialog::getItem(this,tr("请输入条目对话框"),tr("请选择输入一个条目"),items,0,true,&ok); if(ok) { qDebug() << "item:" << item; } }
这里一共创建了4个不同类型的输人对话框。
getText()函数可以提供一个可输入字符串的对话框,各参数的作用分别是:指定父窗口,设置窗口标题、设置对话框中的标签显示文本、设置输入字符串的显示模式(例如密码可以显示成小黑点,这里选择了显示用户输入的实际内容)、设置输入框中的默认字符串和设置获取按下按钮信息的bool变量;
getInt()函数可以提供一个输入整型数值的对话框,其中的参数100表示默认的数值是100,-1000表示可输入的最小值是-1000,1 000表示可输入的最大值是1000,10表示使用箭头按钮,数值每次变化10;
getDouble()函数可以提供一个输入浮点型数值的对话框,其中的参数2表示小数的位数为2;
getItem()函数提供一个可以输入一个条目的对话框,需要先给它提供一些条目,例如这里定义的QStringList类型的items,其中参数0表示默认显示列表中的第0个条目(0就是第一个),参数true 设置条目是否可以被更改,true就是可以被更改。这里使用了静态函数,不过也可以自己定义对象,然后使用相关的函数进行设置。
5.消息对话框
消息对话框QMessageBox类提供了一个模态的对话框来通知用户一些信息,或者向用户提出一个问题并且获取答案。先添加头文件#include<QMessageBox>,然后转到“消息对话框”按钮的单击信号槽中,添加如下代码:
void MyWidget::on_pushButton_3_clicked() { //问题对话框 int ret1 = QMessageBox::question(this,tr("问题对话框"),tr("Do you understand QT?"),QMessageBox::Yes,QMessageBox::No); //提示对话框 int ret2 = QMessageBox::information(this,tr("提示对话框"),tr("This is a book about QT"),QMessageBox::Ok); if(ret2 == QMessageBox::Ok) { qDebug() << tr("提示"); } //警告对话框 int ret3 = QMessageBox::warning(this,tr("警告对话框"),tr("不能提前结束"),QMessageBox::Abort); if(ret3 == QMessageBox::Abort) { qDebug() << tr("警告"); } //错误对话框 int ret4 = QMessageBox::critical(this,tr("严重错误对话框"),tr("发现了一个严重错误,现在要关闭所有文件"),QMessageBox::YesAll); if(ret4 == QMessageBox::YesAll) { qDebug() << tr("错误"); } //关于对话框 QMessageBox::about(this,tr("关于对话框"),tr("hahahhaha")); }
这里创建了4个不同类型的消息对话框,分别拥有不同的图标还有提示音(这个是操作系统设置的),几个参数分别用于设置父窗口、标题栏、显示信息和拥有的按钮。
这里使用的按钮都是QMessageBox类提供的标准按钮。这几个静态函数的返回值就是那些标准按钮,由QMessageBox: : StandardButton枚举类型指定,可以使用返回值来判断用户按下了哪个按钮。about()函数没有返回值,因为它默认只有一个按钮,与其相似的还有一个aboutQt()函数,用来显示现在使用的Qt版本等信息。如果想使用自定义的图标和按钮,那么可以创建QMessageBox类对象,然后使用相关函数进行操作。
6.进度对话框
进度对话框QProgressDialog对一个耗时较长操作的进度提供了反馈。
先添加#include <QProgressDialog>头文件,然后转到“进度对话框”按钮的单击信号槽,更改如下:
void MyWidget::on_pushButton_7_clicked() { QProgressDialog dialog(tr("文件复制进度"),tr("cancel"),0,50000,this); //设置窗口标题 dialog.setWindowTitle(tr("进度对话框")); //将对话框设置为模态 dialog.setWindowModality(Qt::WindowModal); dialog.show(); //演示复制进度 for(int i=0;i<50000;i++) { //设置进度条的当前值 dialog.setValue(i); //避免界面冻结 QCoreApplication::processEvents(); //按下取消按钮则中断 if(dialog.wasCanceled()) { break; } } dialog.setValue(50000); qDebug() << tr("复制结束!"); }
这里首先创建了一个 QProgressDialog类对象dialog,构造函数的参数分别用于设置对话框的标签内容、取消按钮的显示文本、最小值、最大值和父窗口。
然后将对话框设置为模态并进行显示。
for()循环语句模拟了文件复制过程, setValue()函数使进度条向前推进;为了避免长时间操作而使用户界面冻结,必须不断地调用QCoreApplication类的静态函数 processEvents(),可以将它放在 for()循环语句中。使用QProgressDialog 的 wasCanceled()函数来判断用户是否按下了“取消”按钮,如果是,则中断复制过程。这里使用了模态对话框,QProgressDialog还可以实现非模态对话框,不过它需要定时器等的帮助。
7.错误信息对话框
错误信息对话框QErrorMessage类提供了一个显示错误信息的对话框。首先打开mywidget.h文件添加类前置声明,然后添加私有对象:
#ifndef MYWIDGET_H #define MYWIDGET_H #include <QMainWindow> QT_BEGIN_NAMESPACE namespace Ui { class MyWidget; } QT_END_NAMESPACE class QErrorMessage; class MyWidget : public QMainWindow { Q_OBJECT public: MyWidget(QWidget *parent = nullptr); ~MyWidget(); private slots: void on_pushButton_clicked(); void on_pushButton_5_clicked(); void on_pushButton_2_clicked(); void on_pushButton_6_clicked(); void on_pushButton_3_clicked(); void on_pushButton_7_clicked(); private: Ui::MyWidget *ui; QErrorMessage * errordlg; }; #endif // MYWIDGET_H
下面到mywidget.cpp添加头文件#include<QErrorMessage>,并在构造函数中添加如下代码:
MyWidget::MyWidget(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); errordlg = new QErrorMessage(this); }
然后从设计模式转到“错误信息对话框”按钮的单击信号槽添加代码:
void MyWidget::on_pushButton_4_clicked() { errordlg->setWindowTitle(tr("错误信息对话框")); errordlg->showMessage(tr("这里是出错信息!")); }
这里首先新建了一个QErrorMessage对话框,并且调用它的showMessage()函数来显示错误信息,调用这个函数时对话框会以非模态的形式显示出来。错误信息对话框中默认有一个 Show this message again复选框,可以选择以后是否还要显示相同错误信息;为了这个复选框的功能有效,不能像前面几个例子一样在槽中直接创建对话框。
8.向导对话框
向导对话框QWizard类提供了一个设计向导界面的框架。对于向导对话框,读者应该已经很熟悉了,比如安装软件时的向导和创建项目时的向导。QWizard之所以被称为框架,是因为它具有设计一个向导全部的功能函数,可以使用它来实现想要的效果。Qt中包含了Trivial Wizard,License Wizard和 Class Wizard这3个示例程序,可以参考一下。
打开mywidget.h文件,然后添加头文件#include<QWizard>,在 MyWidget类的声明中添加private类型函数声明:
#ifndef MYWIDGET_H #define MYWIDGET_H #include <QMainWindow> #include <QWizard> QT_BEGIN_NAMESPACE namespace Ui { class MyWidget; } QT_END_NAMESPACE class QErrorMessage; class MyWidget : public QMainWindow { Q_OBJECT public: MyWidget(QWidget *parent = nullptr); ~MyWidget(); private slots: void on_pushButton_clicked(); void on_pushButton_5_clicked(); void on_pushButton_2_clicked(); void on_pushButton_6_clicked(); void on_pushButton_3_clicked(); void on_pushButton_7_clicked(); void on_pushButton_4_clicked(); private: Ui::MyWidget *ui; QErrorMessage * errordlg; QWizardPage * createPage1(); QWizardPage * createPage2(); QWizardPage * createPage3(); }; #endif // MYWIDGET_H
这里声明了3个返回值为QWizardPage类对象的指针函数,用来生成3个向导页面。然后在 mywidget.cpp文件中对这3个函数进行定义:
QWizardPage * MyWidget::createPage1() { QWizardPage * page = new QWizardPage; page->setTitle(tr("介绍")); return page; } QWizardPage * MyWidget::createPage2() { QWizardPage * page = new QWizardPage; page->setTitle(tr("用户选择信息")); return page; } QWizardPage * MyWidget::createPage3() { QWizardPage * page = new QWizardPage; page->setTitle(tr("结束")); return page; }
在各个函数中分别新建了向导页面,并且设置了它们的标题。下面转到“向导对话框”按钮的单击信号槽中,更改如下:
void MyWidget::on_pushButton_8_clicked() { QWizard wizard(this); wizard.setWindowTitle("向导对话框"); wizard.addPage(createPage1()); wizard.addPage(createPage2()); wizard.addPage(createPage3()); wizard.exec(); }
这里新建了QWizard类对象,然后使用addPage()函数为其添加了3个页面。这里的参数是QWizardPage类型的指针,可以直接调用生成向导页面函数。运行程序可以看到,向导页面出现的顺序和添加向导页面的顺序是一致的。
上面程序中的向导页面是线性的,而且什么内容也没有添加。如果想设计自己的向导页面,或添加图片、自定义按钮,或设置向导页面顺序等,那么就需要再多了解一下QWizard类和QWizardPage类。
3.3 其他窗口部件
3.3.1 QFreame类族
QFrame类是带有边框的部件的基类。
它的子类包括最常用的标签部件QLabel,另外还有QLCDNumber QSplitter, QStackedWidget、 QToolBox和 QAbstractScrollArea类。
QAbstractScrollArea类是所有带有滚动区域的部件类的抽象基类,这里需要说明,Qt中凡是带有Abstract字样的类都是抽象基类。抽象基类是不能直接使用的,但是可以继承该类实现自己的类,或者使用它提供的子类。QAbstractScrollArea的子类中有最常用的文本编辑器类QTextEdit类和各种项目视图类,这些类会在后面章节中接触到,这里不再讲解。QSplitter会在第4章讲解。
带边框部件最主要的特点就是可以有一个明显的边界框架。QFrame类的主要功能就是用来实现不同的边框效果,这主要是由边框形状(Shape)和边框阴影(Shadow)组合来形成的。QFrame类中定义的主要边框形状如下表所列,边框阴影也如下表所列。这里要说明两个名词:lineWidth和 midLineWidth,其中,lineWidth是边框边界线的宽度;而midLineWidth是在边框中额外插入的一条线的宽度,这条线的作用是为了形成3D效果,并且只在Box,Hline和VLine表现为凸起或者凹陷时有用。QFrame的这些元素组合成的所有边框效果如下图所示。
![]()
下面在程序中演示一下具体效果。新建Qt Widgets应用,项目名称为myframe,选择QWidget为基类,类名MyWidget。完成后打开 mywidget.ui文件,在Qt 设计器中从部件列表里拖入一个Frame到界面上,然后在右下方的属性栏中更改其frameShape为 Box,frameShadow为Sunken,lineWidth为5, midLineWidth为10。
在属性栏中设置部件的属性,这和在源码中用代码实现是等效的,其实也可以直接在mywidget.cpp文件中的 MyWidget构造函数里使用如下代码来代替:
Mywidget::Mywidget(QWidget *parent) : QMainWindow(parent) , ui(new Ui::Mywidget) { ui->setupUi(this); ui -> frame -> setFrameShape(QFrame::Box); ui -> frame -> setFrameShadow(QFrame::Sunken); ui -> frame -> setLineWidth(5); ui -> frame -> setMidLineWidth(10); }
因为下面要讲的部件大都是Qt的标准部件,所以大多会在Qt设计器中直接设置其属性。对于能在属性栏中设置的属性,其类中就一定有相应的函数可以使用代码来实现,只要根据名字在类的参考文档中查找一下即可。
对于QFrame的子类,它们都继承了QFrame的边框设置功能,所以下面对子类的介绍中就不再涉及这方面的内容了,而是讲解各个子类的独有特性。
1.QLable
标签QLabel部件用来显示文本或者图片。在设计器中向界面拖入一个Label,然后将其拖大点,并在属性栏中设置对其方式alignment的属性,水平的改为AlignHCenter,垂直的改为AlignVCenter,这样QLabel中的文本就会在正中间显示。
font 属性可以对字体进行设置,也可以通过代码进行设置,下面打开 mywidget. cpp 文件,在构造函数中添加如下代码或者如下修改:
Mywidget::Mywidget(QWidget *parent) : QMainWindow(parent) , ui(new Ui::Mywidget) { ui->setupUi(this); ui -> frame -> setFrameShape(QFrame::Box); ui -> frame -> setFrameShadow(QFrame::Sunken); ui -> frame -> setLineWidth(5); ui -> frame -> setMidLineWidth(10); QFont font; font.setFamily("宋体"); font.setPointSize(20); font.setBold(true); font.setItalic(true); ui -> label -> setFont(font); }
QFont类提供了对字体的设置,这里使用了“华文行楷”字体族、大小为20、加粗、斜体,通过QLabel的setFont()函数可以使用新建的字体。
QLabel属性栏中的wordWrap属性可以实现文本的自动换行。
如果文本过长时不想自动换行,而是在后面自动省略,那么可以使用QFontMetrics类,该类用来计算给定字体的字符或字符串的大小,其中包含了多个实用函数。要使用QFontMetrics,则可以通过创建对象的方式,或通过QWidget : : fontMetrics()来返回当前部件字体的QFontMetrics对象。下面继续在构造函数中添加代码:
QString string = tr("标题太长,需要进行省略!"); QString str = ui -> label -> fontMetrics().elidedText(string,Qt::ElideRight,180); ui -> label -> setText(str);
QFontMetrics类的elidedText()函数用来进行文本省略,第一个参数用来指定要省略的文本;第二个参数是省略模式,就是“...”省略号出现的位置,包括Qt ::ElideLeft出现在文本开头,Qt: : ElideMiddle出现在文本中间,以及这里使用的Qt ::ElideRight出现在文本末尾;第三个参数是文本的长度,单位是像素,只要第一个参数指定的文本的长度超过了这个值,就会进行省略。可以运行程序,调整参数值,从而查看不同参数的效果。
调参到280。
QLabel属性栏中的scaledContents属性可以实现缩放标签中的内容,比如在标签中放一张较大的图片,则可以选中该属性来显示整个图片。下面来看一下怎么在标签中使用图片。首先在mywidget.cpp文件中添加头文件#include <QPixmap>,然后在构造函数中添加一行代码:
ui -> label -> setPixmap(QPixmap("C:/Users/lhwnb/Desktop/OIP-C.jpg"));
这样就可以在标签中显示图片了。也可以使用相对路径,比如将logo.png图片放到项目目录的 build-myframe-Desktop_Qt_5_9_9_MinGW_32bit-Debug 文件夹中,那么只需要使用相对路径logo.png就可以了。其实,最好的方法是使用资源管理器,将图片放到程序中,这会在第5章讲述。QLabel中还可以显示gif动态图片,在mywidget.cpp 中添加头文件#include<QMovie>,然后在myWidget的构造函数中继续添加代码:
QMovie * movie = new QMovie("路径"); ui -> label -> setMovie(movie); movie -> start();
这时运行程序可以看到,新添加的图片会遮盖以前的图片。
2.QLCDNumber
QLCDNumber部件可以让数码字符显示类似液晶数字一样的效果。从部件栏中拖入一个LCD Number部件到界面上,然后更改其属性:选中 smallDecimalPoint项,这样可以显示小数点;digitCount的作用是设置显示的数字的个数,设置为7,表示要显示7个数字;mode选 Dec表示显示十进制数值,这里还可以设置显示为十六进制(Hex),八进制(Oct)和二进制(Bin)数值; segmentStyle用来设置数码的显示样式,这里提供了3种样式,选择Filled;最后将value设置为456.123,这就是要显示的数值,也可以在代码中使用display()函数来设置要显示的数值。设置好后,运行程序查看效果。在QLCDNumber中可以显示的数码有0/O、1,2,3.4,5/S,6、7,8,9/g、负号,小数点、A,B,C、D、E、F,h,H、L,o、P,r、u、U、Y、冒号、度符号(输人时使用单引号来代替)和空格。
![]()
3.QStackedWidget
QStackedWidget类提供了一个部件栈,可以有多个界面(称为页面),每个界面可以拥有自己的部件,不过每次只能显示一个界面。
这个部件需要使用QComboBox或者QList Widget来选择它的各个页面。在设计模式中向界面上拖入一个List Widget和一个Stacked Widget。在List Widget上右击,在弹出的级联菜单中选择“编辑项目”项,然后在“编辑列表窗口部件”对话框中按下左下角的加号添加两项,并更该名称为“第一页”和“第二页”。
然后在Stacked Widget上拖入一个Label,更改文本为“第一页”,再单击Stacked Widget右上角的小箭头进入下一页,再拖入一个标签,更改文本为“第二页”。然后再将Stacked Widget部件的frameShape属性更改为StyledPanel。
最后,在信号和槽设计模式将 list Widget部件的currentRowChanged ()信号和stacked Widget的 setCurrentIndex()槽关联。
设置完成后运行程序可以看到,现在可以单击list Widget中的项目来选择stackedWidget的页面了。可以在设计模式中在 stackedWidget上右击来为它添加新的页面。
4.QToolBox
QToolBox类提供了一列层叠窗口部件,就像常用的聊天工具QQ中的抽屉效果。从部件栏中选择Tool Box拖入到界面上,右击并在弹出的级联菜单中选择“插入页→在当前页之后”项来新插入一页。
然后更改其frameShape属性为Box,并分别单击各个页的标签,更改其currentItemText分别为“好友”、“黑名单”和“陌生人”。完成后可以运行程序查看效果。
3.3.2 按钮组件
QAbstractButton类是按钮部件的抽象基类,提供了按钮的通用功能。它的子类包括复选框QCheckBox,标准按钮QPushButton,单选框按钮QRadioButton和工具按钮QToolButton。QToolButton会在第5章讲到,这一小节的内容可以参考示例程序Group Box Example。
新建Qt Widgets应用,项目名称mybutton,基类选择QWidget,类名设为MyWidget。完成后在项目文件夹中新建images文件夹,并且放入几张图标图片,供下面编写程序时使用。
1.QPushButton
QPushButton提供一个标准按钮。在项目中打开mywidget.ui文件,拖入3个Push Button到界面上,然后将它们的objectName依次更改为pushBtn1,pushBtn2和pushBtn3。
下面选中 pushBtn1的checkable属性,使得它可以拥有“选中”和“未选中”两种状态;
再选中 pushBtn2的flat属性,可以不显示该按钮的边框。然后转到pushBtn1的toggled( bool)信号的槽,更改如下:
void MyWidget::on_pushButton_toggled(bool checked) { qDebug() << tr("按钮是否按下:") << checked; }
注意添加井include<QDebug>头文件,这时可以运行一下程序查看效果。当pushBtn1处于按下状态的时候checked为 true,否则为false。
下面在MyWidget类的构造函数中添加代码:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); ui -> pushButton -> setText(tr("&nihao")); ui -> pushButton_2 -> setText(tr("帮助(&H)")); ui -> pushButton_2 -> setIcon(QIcon("C:/Users/lhwnb/Desktop/OIP-C.jpg")); ui -> pushButton_3 -> setText(tr("Z&zoom")); QMenu * menu = new QMenu(this); menu -> addAction(QIcon("C:/Users/lhwnb/Desktop/OIP-C.jpg"),tr("放大")); ui -> pushButton_3 -> setMenu(menu); }
注意添加#include<QMenu>头文件。代码里为3个按钮改变了显示文本,在一个字母前加上“&.”符号,则可以将这个按钮的加速键设置为Alt加上这个字母。如果要在文本中显示“&.”符号,可以使用“&.&.”。也可使用setIcon()函数来给按钮添加图标,这里的图片文件使用了绝对路径(当然也可以在设计模式通过更改icon属性来实现)。对于pushBtn3,这里为其添加了下拉菜单,现在这个菜单什么功能也没实现。运行程序查看效果。
2. QCheckBox、QRadioButton和 QGroupBox
对于调查表之类的应用,往往提供多个选项供选择,有些是可以选择多项的,有些只能选择其中一项。
复选框QCheckBox类提供了同时选择多项的功能,而 QRadioButton提供了只能选择一项的功能,一般要把一组按钮放到一个QGroupBox中来管理。
在设计模式时可往界面上拖入两个Group Box,将它们的标题分别改为“复选框”和“单选框”。然后往复选框中拖入3个Check Box,分别更改显示内容为“跑步”“踢球”和“游泳”。再往单选框中拖入3个Radio Button,分别更改其显示内容为“很好”、“一般”和“不好”。
这里还可以选中 Check Box的 tristate属性,让它拥有不改变状态、选中状态和未选中状态3种状态。
对于选择按钮后的操作,可以关联它们的stateChanged()信号和自定义的槽,也可以使用isChecked()函数查看一个按钮是否被选中。除了Group Box,还可以使用QButtonGroup类来管理多个按钮。
这时我点跑步就自动关联上不好了。
3.3.3 QLineEdit
行编辑器QLineEdit部件是一个单行的文本编辑器,它允许用户输入和编辑单行的纯文本内容,而且提供了一系列有用的功能,包括撤销与恢复、剪切和拖放等操作。其中,剪切复制等功能是行编辑自带的,不用自己编码实现,拖放功能会在第5章讲到。这部分内容可以查看Qt的示例程序Line Edits。
新建Qt Widgets应用,项目名称mylineedit,基类QWidget,类名MyWidget。在设计模式时可往界面上拖入几个标签和Line Edit,设计界面如图3-13所示。然后将4个Line Edit从上到下依次更改其objectName为 lineEdit1,lineEdit2 、 lineEdit3和 lineEdit4。
1.显示模式
行编辑器QLineEdit有4种显示模式(echoMode),可以在 echoMode属性中更改它们,分别是:
Normal正常显示输入的信息;
NoEcho不显示任何输入,这样可以保证不泄露输入的字符位数;
Password显示为密码样式,就是以小黑点或星号之类的字符代替输人的字符;PasswordEchoOnEdit在编辑时显示正常字符,其他情况下显示为密码样式。
这里设置lineEdit1的echoMode为 Password。
2.输入掩码
QLineEdit提供了输入掩码(inputMask)来限制输入的内容。可以使用一些特殊的字符来设置输入的格式和内容,这些字符中有的起限制作用且必须要输入一个字符,有的只是起限制作用,但可以不输入字符而是以空格代替。先来看一下这些特殊字符的含义,如下表所列。
输入掩码 字符(必须输入) 字符(可留空) 含义 A a 只能输入A~Z,a~z N n 只能输入A~Z,a~z,0-9 X x 可以输入任意字符 9 0 只能输入0-9 D d 只能输入1-9 # 只能输入加号+,减号-,0-9 H h 只能输入十六进制字符,A~F,a~f,0~9 B b 只能输入二进制字符0/1 > 后面的字母字符自动转换为大写 < 后面的字母字符自动转换为小写 ! 停止字符的大小写转换 \ 将该表中的特殊字符正常显示用作分隔符 下面将lineEdit2的inputMask属性设置为“>AA-90-bb-!aa\#H;*”,其含义为:
“>”号表明后面输入的字母自动转为大写;
“AA”表明开始必须输入两个字母,因为有前面的“>”号的作用,所以输入的这两个字母会自动变为大写;
“-”号为分隔符,直接显示,该位不可输入;
“9”表示必须输人一个数字;“0”表示输人一个数字,或者留空;
“bb”表示这两位可以留空,或者输人两个二进制字符,即0或1;
“!”表明停止大小写转换,就是在最开始的“>”号不再起作用;
“aa”表示可以留空,或者输入两个字母;
“\#”表示将“#”号作为分隔符,因为“#”号在这里有特殊含义,所以前面要加上“\”号;
“H”表明必须输入一个十六进制的字符;
“;*”表示用“*”号来填充空格。
另外,也可以使用setInputMask()函数在代码中来设置输入掩码。
在lineEdit2上右击,然后转到它的returnPressed()回车键按下信号的槽中。更改如下:
void MyWidget::on_lineEdit2_returnPressed() { //让lineEdit3获得焦点 ui -> lineEdit3 -> setFocus(); //输出lineEdit2的内容 qDebug() << ui -> lineEdit2 -> text(); //输出lineEdit2显示的内容 qDebug() << ui -> lineEdit2 -> displayText(); }
注意要添加#include <QDebug>头文件。这里先让下一个行编辑器获得焦点然后输出了lineEdit2的内容和显示出来的内容,它们有时是不一样的,编程时更多的是使用text()函数来获取它的内容。这时运行程序进行输入,完成后按下回车键,可以看一下输出的内容。这里还要说明一点,如果没有输入完那些必须要输入的字符,按下回车键是不起作用的。
3.输入验证
在QLineEdit中还可以使用验证器(validator)来对输入进行约束。在mywidget.cpp文件的构造函数中添加代码:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //新建验证器 100-999 QValidator * validator = new QIntValidator(100,999,this); ui -> lineEdit3 -> setValidator(validator); }
在代码中为lineEdit3添加了验证器,那么它现在只能输入100~999之间的数字。再进入 lineEdit3的回车键按下信号的槽,输出lineEdit3的内容。然后运行程序会发现,其他的字符无法输入,而输入小于100 的数字时,按下回车键也是没有效果的。
QValidator中还提供了QDoubleValidator,可以用它来设置浮点数。
如果想设置更强大的字符约束,就要使用正则表达式了,这个在第7章会讲到。
4.自动补全
QLineEdit中也提供了强大的自动补全功能,这是利用QCompleter类实现的。在My Widget类的构造函数中继续添加代码:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //新建验证器 100-999 QValidator * validator = new QIntValidator(100,999,this); ui -> lineEdit3 -> setValidator(validator); QStringList wordlist; wordlist << "Qt" << "Qt Creator" << tr("你好"); //新建自动完成器 QCompleter * completer = new QCompleter(wordlist,this); //设置大小写不敏感 completer -> setCaseSensitivity(Qt::CaseSensitive); ui -> lineEdit_4 -> setCompleter(completer); }
要添加#include<QCompleter>头文件,运行程序,在最后一个行编辑器中输入“Q”,则自动出现“Qt”和“Qt Creator”两个选项。
QCompleter的使用可以参考Qt的示例程序Completer。
3.3.4 QAbstractSpinBox
QAbstractSpinBox类是一个抽象基类,提供了一个数值设定框和一个行编辑器来显示设定值。它有3个子类QDateTimeEdit 、QSpinBox和QDoubleSpinBox,分别用来完成日期时间、整数和浮点数的设定。这一小节内容可以查看Spin Boxes示例程序。
新建Qt Widgets应用,项目名称myspinbox,基类为QWidget,类名MyWidget。
1. QDateTimeEdit
QDateTimeEdit类提供了一个可以编辑日期和时间的部件。
到设计模式,从部件栏中分别拖Time Edit ,Date Edit和Date/Time Edit到界面上,然后设置timeEdit的displayFormat为“h:mm:ssA”,这就可以使用12h制来进行显示。
对于 dateEdit,选中它的calendarPopup属性,就可以使用弹出的日历部件来设置日期。
然后在 MyWid-get类的构造函数中添加代码:
MyWidget::MyWidget(QWidget *parent) : QWidget(parent) , ui(new Ui::MyWidget) { ui->setupUi(this); //设置时间为当前系统的时间 ui -> dateTimeEdit -> setDateTime(QDateTime::currentDateTime()); //设置时间显示格式 ui -> dateTimeEdit -> setDisplayFormat(tr(" yyyy 年 MM 月 dd 日 ddd HH 时 mm 分 ss 秒")); }
这里使用代码设置了dateTimeEdit中的日期和时间。简单说明一下:
y表示年;
M表示月;
d表示日;
ddd表示星期;
H表示小时,使用24 h制显示,而h也表示小时,如果最后有AM或者PM的,则是12h制显示,否则使用24 h制;
m表示分;
s表示秒;
还有一个z可以用来表示毫秒。
更多的格式可以参考QDateTime类。现在运行程序查看效果。还要说明,可以使用该部件的text()函数获取设置的值,它返回QString 类型的字符串;也可以使用dateTime()函数﹐它返回的是QDateTime类型数据。
2.QSpinBox和 QDoubleSpinBox
QSpinBox用来设置整数, QDoubleSpinBox用来设置浮点数,这两个部件在前面的输入对话框中已经接触过了。
从部件栏中找到Spin Box和 Double Spin Box,并将它们拖入到界面上。可以在属性栏中看到spinBox的属性有:后缀suffix属性,可以设置为“%”,这样就可以显示百分数了;前缀 prefix属性,比如表示金钱时前面有“¥”字符;最小值minimum属性,设置其最小值;最大值 maximum属性设置其最大值;单步值singleStep属性设置每次增加的数值,默认为1;value为现在显示的数值。
而 doubleSpinBox又增加了一个小数位数decimals属性,用来设置小数点后面的位数。
关于这两个部件就不再过多讲述,最后提醒大家,可以在代码中使用value()函数来获取设置的数值。
3.3.5 QAbstractSlider
QAbstractSlider类用于提供区间内的一个整数值,它有一个滑块,可以定位到一个整数区间的任意值。该类是一个抽象基类,它有3个子类QScrollBar ,QSlider 和QDial。其中,滚动条QScrollBar 多数是用在QScrollArea类中来实现滚动区域;QSlider就是常见的音量控制或多媒体播放进度等滑块部件;QDial是一个刻度表盘部件。这些部件可以参考Sliders示例程序。
新建Qt Widgets应用,项目名称myslider,基类选择QWidget,类名为MyWidget。完成后到设计模式,从部件栏中分别将Dial、Horizontal Scroll Bar 和 Vertical Scroll Bar、 Horizontal Slider 以及Vertical Slider等部件拖入到界面上。
先看两个 Scroll Bar的属性:maximum属性用来设置最大值, minimum属性用来设置最小值;singleStep属性是每步的步长,默认是1,就是按下方向键后其数值增加或者减少1;pageStep是每页的步长,默认是10,就是按下PageUp或者PageDown按键后,其数值增加或者减少10;value与sliderPosition是当前值;tracking设置是否跟踪,默认为是,就是在拖动滑块时,每移动一个刻度,都会发射valueChanged()信号,如果选择否,则只有拖动滑块释放时才发射该信号;orientation设置部件的方向,有水平和垂直两种选择;invertedAppearance属性设置滑块所在的位置,比如默认滑块开始在最左端,选中这个属性后,滑块默认就会在最右端。invertedControls设置反向控制,比如默认是向上方向键是增大,向下方向键是减小,如果选中这个属性,那么控制就会正好反过来。另外,为了使部件可以获得焦点,需要将focusPolicy设置为StrongFocus。
再来看两个Slider,它们有了自己的两个属性 tickPosition和 tickInterval,前者用来设置显示刻度的位置,默认是不显示刻度;后者是设置刻度的间隔。而 Dial有自己的属性wrapping,用来设置是否首尾相连,默认开始与结束是分开的;属性notchTarget用来设置刻度之间的间隔;属性 notchesVisible用来设置是否显示刻度。
再往界面上拖人一个 Spin Box,然后进入信号和槽编辑界面,将刻度表盘部件dial的sliderMoved(int)信号分别与其他各个部件的setValue(int)槽相连接。设置完成后运行程序,然后使用鼠标拖动刻度盘部件的滑块,可以看到其他所有的部件都跟着变化了。
随着表盘的转动各个部件也开始转动了。
3.4 小结
本章讲述了众多常用窗口部件的使用方法,其中还涉及了程序调试、信号和槽等知识。学习完本章,读者没必要把所有讲到的部件都熟练掌握,只要心中有个印象,大概了解各个部件实现的功能即可,以后使用时可以再回过头来参考学习。最重要的是掌握程序的创建流程和各个部件类之间的相互关系,而且要多应用信号,槽以及qDebug()函数,这是以后Qt编程中经常要用到的。