您的位置:首页 > 编程语言 > Qt开发

QT学习笔记

2016-05-31 00:40 246 查看
QT学习之路2原文目录

由于教程的前3章都是概念和配置的基础内容,故不做摘记。后文直接从第4章开始:

4)信号槽

按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。

在 Qt 5 中,QObject::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 *,
Qt::ConnectionType) const;

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)

QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);


这五个重载的返回值都是QMetaObject::Connection,现在我们不去关心这个返回值。

connect(sender,   signal,
receiver, slot);


这是我们最常用的形式。connect()一般会使用前面四个参数,第一个是发出信号的对象,第二个是发送对象发出的信号,第三个是接收信号的对象,第四个是接收对象在接收到信号之后所需要调用的函数。也就是说,当 sender 发出了 signal 信号之后,会自动调用 receiver 的 slot 函数。由此我们可以看出,connect()函数,sender 和 receiver 没有什么区别,都是QObject指针;主要是 signal
和 slot 形式的区别。

信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。

5)自定义信号槽

只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。因此,如果你觉得你的类不需要使用信号槽,就不添加这个宏,就是错误的。其它很多操作都会依赖于这个宏。注意,这个宏将由
moc(我们会在后面章节中介绍 moc。这里你可以将其理解为一种预处理器,是比 C++ 预处理器更早执行的预处理器。) 做特殊处理,不仅仅是宏展开这么简单。moc 会读取标记了 Q_OBJECT 的头文件,生成以 moc_ 为前缀的文件,比如 newspaper.h 将生成 moc_newspaper.cpp。

注意,由于 moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明。因此,如果我们的Newspaper和Reader类位于 main.cpp 中,是无法得到 moc 的处理的。许多初学者会遇到莫名其妙的错误,一加上Q_OBJECT就出错,很大一部分是因为没有注意到这个宏应该放在头文件中。

signals 块所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现。

emit newPaper(m_name);emit
是 Qt 对 C++ 的扩展,是一个关键字(其实也是一个宏)。emit 的含义是发出,也就是发出newPaper()信号。感兴趣的接收者会关注这个信号,可能还需要知道是哪份报纸发出的信号?所以,我们将实际的报纸名字m_name当做参数传给这个信号。当接收者连接这个信号时,就可以通过槽函数获得实际值。这样就完成了数据从发出者到接收者的一个转移。

与信号函数不同,槽函数必须自己完成实现代码。槽函数就是普通的成员函数,因此作为成员函数,也会受到 public、private 等访问控制符的影响。(我们没有说信号也会受此影响,事实上,如果信号是 private 的,这个信号就不能在类的外面连接,也就没有任何意义。)

总结一下自定义信号槽需要注意的事项:

发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
使用 emit 在恰当的位置发送信号;
使用QObject::connect()函数连接信号和槽。


6)Qt 模块简介

本节主要对 Qt 5 的模块进行一个简单的介绍,以便以后大家需要哪些功能的时候知道到哪个模块去寻找。

具体内容直接到原文里看

7)MainWindow 简介
QMainWindow是 Qt 框架带来的一个预定义好的主窗口类。所谓主窗口,就是一个普通意义上的应用程序(不是指游戏之类的那种)最顶层的窗口。经典的主窗口,通常是由一个标题栏,一个菜单栏,若干工具栏和一个任务栏。在这些子组件之间则是我们的工作区。事实上,QMainWindow正是这样的一种布局。



主窗口的最上面是 Window Title,也就是标题栏,通常用于显示标题和控制按钮,比如最大化、最小化和关闭等。Window Title 下面是 Menu Bar,也就是菜单栏,用于显示菜单。窗口最底部是 Status Bar,称为状态栏。除去上面说的三个横向的栏,中间是以矩形区域表示。我们可以看出,最外层称为 Tool Bar Area,用于显示工具条区域。之所以是矩形表示,是因为,Qt 的主窗口支持多个工具条。你可以将工具条拖放到不同的位置,因此这里说是
Area。在工具条区域内部是 Dock Widget Area,这是停靠窗口的显示区域。所谓停靠窗口,就像 Photoshop 的工具箱一样,可以停靠在主窗口的四周,也可以浮动显示。主窗口最中间称为 Central Widget,就是我们程序的工作区。通常我们会将程序最主要的工作区域放置在这里,类似 Word 的稿纸或者 Photoshop 的画布等等。

在 pro 文件中,我们通常需要这么来写:

QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET    = qtdemo
TEMPLATE  = app

SOURCES  += main.cpp \
mainwindow.cpp

HEADERS  += mainwindow.h


简单解释一下 pro 文件。首先,我们定义了 QT,用于告诉编译器,需要使用哪些模块。这些模块都在前面章节中有过介绍。我们通常需要添加 core 和 gui。第二行,如果 Qt 的主版本号(QT_MAJOR_VERSION)大于 4,也就是 Qt 5,则需要另外添加 widgets(因为在 Qt 5 中,所有组件都是在 widgets 模块定义的)。TARGET 是生成的程序的名字。TEMPLATE 是生成 makefi
d075
le 所使用的模板,比如 app 就是编译成一个可执行程序,而 lib 则是编译成一个链接库(默认是动态链接库)。SOURCES
和 HEADERS 顾名思义,就是项目所需要的源代码文件和头文件。现在,我们只需使用默认的 pro 文件即可。以后随着项目的不断增大,pro 文件通常会非常复杂。

8)添加动作

Qt 使用QAction类作为动作。我们把QAction对象添加到菜单,就显示成一个菜单项,添加到工具栏,就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。

tr()函数一个用于 Qt 国际化的函数,我们可以使用 Qt 提供的国际化工具,将tr()函数的字符串提取出来,进行国际化。tr()函数里面一般会是英文文本。

在QAction构造函数,我们传入了一个图标、一个文本和 this 指针。

QIcon的参数,以 : 开始,意味着从资源文件中查找资源。:/images/doc-open就是找到了这里的 document-open.png 这个文件。(我们使用的是 png 格式的图片,这是 Qt 内置支持的图片格式。其他格式的图片,比如 jpg、gif 则需要插件支持。这些插件实际已经随着 Qt 一同发布。)

tr("&Open...")文本值前面有一个 &,意味着这将成为一个快捷键。setShortcut()函数,用于说明这个QAction的快捷键。

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
setWindowTitle(tr("Main Window"));//设置窗口标题

openAction = new QAction(QIcon(":/images/doc-open"), tr("&Open..."), this);//新建一个QAction控件,传入图标地址,快捷键和this指针
openAction->setShortcuts(QKeySequence::Open);//为其分配QKeySequence为我们定义的内置快捷键
openAction->setStatusTip(tr("Open an existing file"));//当用户鼠标滑过这个action时,主窗口下方的状态栏会显示相应的提示。
connect(openAction, &QAction::triggered, this, &MainWindow::open);
//将这个QAction的triggered()信号与MainWindow类的open()函数连接起来。当用户点击了这个QAction时,会自动触发MainWindow的open()函数。
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);

QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
/*menuBar()、toolBar()和statusBar()三个是QMainWindow的函数,用于创建并返回菜单栏、工具栏和状态栏。我们可以从代码清楚地看出,我们向菜单栏添加了一个 File 菜单,并且把这个QAction对象添加到这个菜单;同时新增加了一个 File 工具栏,也把QAction对象添加到了这个工具栏。我们可以看到,在菜单中,这个对象被显示成一个菜单项,在工具栏变成了一个按钮。*/

statusBar() ;
}


9)资源文件

Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。

具体建立流程见原文

10)对象模型

在使用标准 C++ 编译器编译 Qt 源程序之前,Qt 先使用一个叫做 moc(Meta Object Compiler,元对象编译器)的工具,先对 Qt 源代码进行一次预处理(注意,这个预处理与标准 C++ 的预处理有所不同。Qt 的 moc 预处理发生在标准 C++ 预处理器工作之前,并且 Qt 的 moc 预处理不是递归的。),生成标准 C++ 源代码,然后再使用标准 C++ 编译器进行编译。

Qt 使用 moc,为标准 C++ 增加了一些特性:

信号槽机制,用于解决对象之间的通讯,这个我们已经了解过了,可以认为是 Qt 最明显的特性之一;
可查询,并且可设计的对象属性;
强大的事件机制以及事件过滤器;
基于上下文的字符串翻译机制(国际化),也就是 tr() 函数,我们简单地介绍过;
复杂的定时器实现,用于在事件驱动的 GUI 中嵌入能够精确控制的任务集成;
层次化的可查询的对象树,提供一种自然的方式管理对象关系。
智能指针(QPointer),在对象析构之后自动设为 0,防止野指针;
能够跨越库边界的动态转换机制。

通过继承QObject类,我们可以很方便地获得这些特性。

对象树:QObject是以对象树的形式组织起来的。当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。

QWidget是能够在屏幕上显示的一切组件的父类。QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。

当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。Qt 保证的是,任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。

{
QWidget window;
QPushButton quit("Quit", &window);
}


我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。

11)布局管理器

你只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。下面来看一个例子:

int main(int argc, char *argv[])
{
QApplication app(argc, argv);

QWidget window;//创建一个主窗体
window.setWindowTitle("Enter your age");//设置主窗体标题栏信息

QSpinBox *spinBox = new QSpinBox(&window);//在堆上创建一个计数控件,作为window的子对象
QSlider *slider = new QSlider(Qt::Horizontal, &window);//在堆上创建一个(水平)滑条控件,作为window的子对象
spinBox->setRange(0, 130);//设定计数控件计数范围
slider->setRange(0, 130);//设定滑条控件计数范围

QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);//把滑条控件上滑块的变化链接到计数控件上
void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;//新建一个指针指向<span style="font-family: Arial, Helvetica, sans-serif;">QSpinBox::valueChanged信号函数,并指定int参数</span>
QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);//把之前指定的信号函数链接到setvalue函数上,完成双向绑定
spinBox->setValue(35);//指定两者的默认值

QHBoxLayout *layout = new QHBoxLayout;//创建一个QHBoxLayout对象。显然,这就是一个布局管理器。
layout->addWidget(spinBox);//将这两个组件都添加到这个布局管理器
layout->addWidget(slider);
window.setLayout(layout);//把该布局管理器设置为窗口的布局管理器

window.show();

return app.exec();
}

12)菜单栏、工具栏和状态栏

QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);

menuBar()是QMainWindow提供的函数,因此你是不会在QWidget或者QDialog中找到它的。这个函数会返回窗口的菜单栏,如果没有菜单栏则会新创建一个。这也就解释了,为什么我们可以直接使用menuBar()函数的返回值,毕竟我们并没有创建一个菜单栏对象啊!原来,这就是menuBar()为我们创建好并且返回了的。

当我们创建出来了菜单对象时,就可以把QAction添加到这个菜单上面,也就是addAction()函数的作用。

13)对话框简介

Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。

模态对话框/非模态对话框:是否阻塞对其他对话框的操作

void MainWindow::open()
{
QDialog *dialog = new QDialog;//在堆上建立一个对话框对象
dialog->setAttribute(Qt::WA_DeleteOnClose);//setAttribute()函数设置对话框关闭时,自动销毁对话框
dialog->setWindowTitle(tr("Hello, dialog!"));//设置对话框的标题栏内容
dialog->show();//显示对话框
}

14)对话框数据传递

void MainWindow::open()
{
QDialog dialog(this);
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();//显示一个模态对话框
qDebug() << dialog.result();//语句,将后面的信息输出到标准输出,一般就是控制台
}


用qDebug()需要引入头文件。在exec()函数之后,我们直接可以获取到 dialog 的数据值。注意,exec()开始了一个事件循环,代码被阻塞到这里。由于exec()函数没有返回,因此下面的result()函数也就不会被执行。直到对话框关闭,exec()函数返回,此时,我们就可以取得对话框的数据。

一般我们会使用类似下面的代码:

QDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
// do something
} else {
// do something else
}

来判断对话框的返回值,也就是用户是点击了“确定”还是“取消”

15)标准对话框 QMessageBox

Qt 的内置对话框大致分为以下几类:

QColorDialog:选择颜色;
QFileDialog:选择文件或者目录;
QFontDialog:选择字体;
QInputDialog:允许用户输入一个值,并将其值返回;
QMessageBox:模态对话框,用于显示信息、询问问题等;
QPageSetupDialog:为打印机提供纸张相关的选项;
QPrintDialog:打印机配置;
QPrintPreviewDialog:打印预览;
QProgressDialog:显示操作过程。
具体函数及使用方法见原文及QT文档
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: