说明
本博总结的是豆子先生写的Qt 学习之路2的内容。
窗口部件
基础窗口部件QWidget
//Qt::FramelessWindowHint删除了边框
QWidget *widget = new QWidget(0, Qt::Dialog | Qt::FramelessWindowHint);
//Qt::WindowStaysOnTopHint使窗口置于所有窗口之上
QLabel *label = new QLabel(0, Qt::SplashScreen | Qt::WindowStaysOnTopHint);
返回窗口位置信息:
//返回位置信息
widget.pos();
//返回不包含边框的窗口内部矩形,左上角是(0,0)点
widget.rect();
//返回不包含边框窗口的内部矩形
widget.size();
//返回窗口内部宽高
widget.width();
widget.height();
//调整窗口大小
widget.resize();
信号槽
1.信号和槽
所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。
我们关键留意connect函数,它的常用形式:
//connect(sender, signal, receiver, slot);
//发出信号对象,发出的信号,接收信号的对象,接收到信号后需要调用的函数
QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);
connect函数有五个重载:
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
const Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
2.使用信号槽实现观察者模式效果
定义一个报纸类Newspaper,一个订阅者类Subscriber,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。
在经典的实现代码中,观察者会将自身注册到被观察者的一个容器中(比如subscriber.registerTo(newspaper))。被观察者发生了任何变化的时候,会主动遍历这个容器,依次通知各个观察者(newspaper.notifyAllSubscribers())。
newspaper.h
//继承QObject才能使用信号槽
#include <QObject>
class Newspaper : public QObject
{
//凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。
//不管是不是使用信号槽,都应该添加这个宏。(放在头文件里)
Q_OBJECT
public:
Newspaper(const QString & name) :
m_name(name)
{
}
//信号作为函数名,不需要在 cpp 函数中添加任何实现
//没有实现的函数名能通过编译,是因为 moc 会帮我们实现信号函数所需要的函数体
void send()
{
//emit 是一个关键字(其实也是一个宏)。
//emit 的含义是发出,也就是发出newPaper()信号。
emit newPaper(m_name);
}
signals:
void newPaper(const QString &name);
private:
QString m_name;
};
reader.h
#include <QObject>
#include <QDebug>
class Reader : public QObject
{
Q_OBJECT
public:
Reader() {}
//槽函数必须实现,同时也会收到public、private约束
void receiveNewspaper(const QString & name)
{
qDebug() << "Receives Newspaper: " << name;
}
};
main.cpp
#include <QCoreApplication>
#include "newspaper.h"
#include "reader.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
Newspaper newspaper("Newspaper A");
Reader reader;
QObject::connect(&newspaper, &Newspaper::newPaper,
&reader, &Reader::receiveNewspaper);
//报纸类发出信号
newspaper.send();
return app.exec();
}
当我们运行上面的程序时,会看到终端输出 Receives Newspaper: Newspaper A 这样的字样。
Qt模块
Qt 基础模块分为以下几个:
- Qt Core,提供核心的非 GUI 功能,所有模块都需要这个模块。这个模块的类包括了动画框架、定时器、各个容器类、时间日期类、事件、IO、JSON、插件机制、智能指针、图形(矩形、路径等)、线程、XML 等。所有这些类都可以通过 头文件引入。
- Qt Gui,提供 GUI 程序的基本功能,包括与窗口系统的集成、事件处理、OpenGL 和 OpenGL ES 集成、2D 图像、字体、拖放等。这些类一般由 Qt 用户界面类内部使用,当然也可以用于访问底层的 OpenGL ES 图像 API。Qt Gui 模块提供的是所有图形用户界面程序都需要的通用功能。
- Qt Multimedia,提供视频、音频、收音机以及摄像头等功能。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += multimedia。
- Qt Network,提供跨平台的网络功能。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += network。
- Qt Qml,提供供 QML(一种脚本语言,也提供 JavaScript 的交互机制) 使用的 C++ API。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += qml。
- Qt Quick,允许在 Qt/C++ 程序中嵌入 Qt Quick(一种基于 Qt 的高度动画的用户界面,适合于移动平台开发)。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += quick。
- Qt SQL,允许使用 SQL 访问数据库。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += sql。
- Qt Test,提供 Qt 程序的单元测试功能。这些类可以通过 引入,而且需要在 pro 文件中添加 QT += testlib。
- Qt Webkit,基于 WebKit2 的实现以及一套全新的 QML API(顺便说一下,Qt 4.8 附带的是 QtWebkit 2.2)。
添加动作QAction
QAction包含了图标、菜单文字、快捷键、状态栏文字、浮动帮助等信息。当把一个QAction对象添加到程序中时,Qt 自己选择使用哪个属性来显示,无需我们关心。
在QMainWindow中使用QAction:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
void open();
QAction *openAction;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include <QAction>
#include <QMenuBar>
#include <QMessageBox>
#include <QStatusBar>
#include <QToolBar>
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
//tr()函数,提取字符串,进行国际化
setWindowTitle(tr("Main Window"));
//使用QIcon传入图像,输入的是字符串(图像路径)
//第二个参数中,文本前加"&"表示是快捷键
openAction = new QAction(QIcon(":/images/doc-open"), tr("&Open..."), this);
//setShortcut()用于说明 QAction 的快捷键
//QKeySequence定义了内置的快捷键,根据平台不同而改变
openAction->setShortcuts(QKeySequence::Open);
//实现了当用户鼠标滑过这个 action 时,会在主窗口下方的状态栏显示相应的提示。
openAction->setStatusTip(tr("Open an existing file"));
//此处connect函数将这个QAction的triggered()信号与MainWindow类的open()函数连接起来。
connect(openAction, &QAction::triggered, this, &MainWindow::open);
//向菜单栏添加了一个 File 菜单,并且把这个QAction对象添加到这个菜单
//menuBar()是QMainWindow提供的函数
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
//同时新增加了一个 File 工具栏,也把QAction对象添加到了这个工具栏
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
statusBar() ;
}
MainWindow::~MainWindow()
{
}
void MainWindow::open()
{
QMessageBox::information(this, tr("Information"), tr("Open"));
}
对象模型
标准 C++ 对象模型在运行时效率方面卓有成效,但是在某些特定问题域下的静态特性就显得捉襟见肘。GUI 界面需要同时具有运行时的效率以及更高级别的灵活性。为了解决这一问题,Qt “扩展”了标准 C++。所谓“扩展”,实际是在使用标准 C++ 编译器编译 Qt 源程序之前,Qt 先使用一个叫做 moc(Meta Object Compiler,元对象编译器)的工具,先对 Qt 源代码进行一次预处理(注意,这个预处理与标准 C++ 的预处理有所不同。Qt 的 moc 预处理发生在标准 C++ 预处理器工作之前,并且 Qt 的 moc 预处理不是递归的。),生成标准 C++ 源代码,然后再使用标准 C++ 编译器进行编译。
通过继承QObeject类可以使用这个特性。
Qt 对象树
在MainWindow中的 parent 指针的作用:QObject是以对象树的形式组织起来的。当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
QWidget是能够在屏幕上显示的一切组件的父类。QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
当QObject在栈上创建:
{
QWidget window;
QPushButton quit("Quit", &window);
}
这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
布局管理
只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QWidget window;
window.setWindowTitle("Enter your age");
QSpinBox *spinBox = new QSpinBox(&window);
QSlider *slider = new QSlider(Qt::Horizontal, &window);
spinBox->setRange(0, 130);
slider->setRange(0, 130);
//两个connect函数完成双向的数据绑定
QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);
//因为QSpinBox有两个信号:
//valueChanged(int)和valueChanged(const QString&)
//编译器会不知道调用那个函数,故创建的时候指定这个函数指针参数为int
void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;
QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);
spinBox->setValue(35);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(spinBox);
layout->addWidget(slider);
window.setLayout(layout);
window.show();
return app.exec();
}
Qt 提供了几种布局管理器:
- QHBoxLayout:按照水平方向从左到右布局;
- QVBoxLayout:按照竖直方向从上到下布局;
- QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;
- QFormLayout:按照表格布局,每一行前面是一段文本,文本后面跟随一个组件(通常是输入框),类似 HTML 的 form;
- QStackedLayout:层叠的布局,允许我们将几个组件按照 Z 轴方向堆叠,可以形成向导那种一页一页的效果。
菜单栏、工具栏和状态栏
QMenuBar代表的是窗口最上方的一条菜单栏。我们使用其addMenu函数为其添加菜单。尽管我们只是提供了一个字符串作为参数,但是 Qt 为将其作为新创建的菜单的文本显示出来。
QToolBar就是工具栏。我们使用的是addToolBar()函数添加新的工具栏。
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
QToolBar *toolBar2 = addToolBar(tr("Tool Bar 2"));
toolBar2->addAction(openAction);
对话框
简介
Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框。
void MainWindow::open()
{
//没有设置对话框的parent指针
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
}
显示该对话框会作为一个顶层窗口,出现在屏幕的中间。
void MainWindow::open()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
}
出现在父窗口的中间。
对话框分为模态对话框和非模态对话框。
- 模态对话框:就是会阻塞同一应用程序中其它窗口的输入。
- 非模态对话框:例如查找搜索框。
同时模态对话框又分:
- 应用程序级别的模态:当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关闭对话框,然后才能访问程序中其他的窗口。
- 窗口级别的模态:该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它窗口交互。
Qt 使用QDialog::exec()实现应用程序级别的模态对话框,使用QDialog::open()实现窗口级别的模态对话框,使用QDialog::show()实现非模态对话框。
使用show()实现非模态:
void MainWindow::open()
{
//这里使用QDialog dialog(this);的话会看不到结果
//因为show不会阻塞线程,show返回,open函数结束,对话框被析构
QDialog *dialog = new QDialog;
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
}
对话框数据传递
模态对话框使用了**exec()**函数将其显示出来。exec()函数的真正含义是开启一个新的事件循环。所谓事件循环,可以理解成一个无限循环。Qt 在开启了事件循环之后,系统发出的各种事件才能够被程序监听到。这个事件循环相当于一种轮询的作用。既然是无限循环,当然在开启了事件循环的地方,代码就会被阻塞,后面的语句也就不会被执行到。因此,对于使用了exec()显示的模态对话框,我们可以在exec()函数之后直接从对话框的对象获取到数据值。
示例代码:
void MainWindow::open()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
//这里执行exec会阻塞代码,result会一直无法输出直到关闭窗口
dialog.exec();
//qDebug类似于std::cout
qDebug() << dialog.result();
}
exec是有返回值的:Accepted或者Rejected。
一般根据返回值判断用户点击的是“确认”还是“取消”:
QDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
// do something
} else {
// do something else
}