Qt学习笔记

QT学习笔记

  • 一,从Hello World 开始
  • 二,信号槽
  • 二,自定义信号槽
  • 三,Qt模块
  • 四,MainWIndow
  • 五,对MainWindow添加具体功能
  • 六,资源管理
  • 七,对象模型
  • 八,布局管理器
  • 九,菜单栏、工具栏和状态栏
  • 十,对话框
    • 1,对话框简介
    • 2,对话框间的数据传递
    • 3,标准对话框QMessageBox

一,从Hello World 开始

引例

#include 
#include 

int main(int argc, char* argv[])
{
     
    //创建QApplication实例,这是必不可少的
    QApplication app(argc, argv);

    //创建QLabel实例,对构造函数赋值Hello, world文本,这是为了能在QLabel上显示这一条文本
    QLabel label("Hello, world");
    //调用show()方法将其显示出来
    label.show();

    //调用app.exec(),从而开启事件循环,试想,若不开启事件循环,那么main函数会立即退出,QLabel对象就不能一直显示了
    return app.exec();
}

补充:

#include 
#include 

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

    //不建议这样做
    QLabel* label = new QLabel("Hello, world");
    label->show();

    return app.exec();
}

二,信号槽

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

例子:

#include 
#include 

int main(int argc, char* argv[])
{
     
    QApplication app(argc, argv);
	
	//按钮在QT中被称为QPushButton
    QPushButton firstButton("Quit");
    
    QObject::connect(&firstButton, &QPushButton::clicked, &QApplication::quit);
    firstButton.show();
    
    return app.exec();
}

详解QObject::connect():
该函数让我们连接系统提供的信号和槽

该函数最常用的一般形式:

connect(sender, signal, receiver, slot);

sender:发出信号的对象
signal:发送对象发出的信号
receiver:接收信号的对象
slot:接收对象在接收信号后会自动调用的函数

sender对象发出信号signal后,会自动调用receiver对象的slot函数

在Qt中,有五个重载:

//将signal和slot作为字符串处理
QMetaObject::Connection connect(const QObject *, const char *,
                                const QObject *, const char *,
                                Qt::ConnectionType);

//可以将每个函数视为QMetaMethod的子类,该写法可使用QMetaMethod来进行类型对比
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
                                const QObject *, const QMetaMethod &,
                                Qt::ConnectionType);

//可见缺少了receiver,这是将this指针作为receiver
QMetaObject::Connection connect(const QObject *, const char *,
                                const char *,
                                Qt::ConnectionType) const;

//PointerToMemberFunction,指向成员函数的指针
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                const QObject *, PointerToMemberFunction,
                                Qt::ConnectionType)

//Functor类型,可接受static函数、全局函数和Lambda表达式
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
                                Functor);		

信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。如果不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少),但是不能说信号根本没有这个数据,你就要在槽函数中使用(就是槽函数的参数比信号的多,这是不允许的)。

将一个信号连接到Lambda表达式:

  • 待学习Lambda表达式后回看

二,自定义信号槽

经典的观察者模式在讲解举例的时候通常会举报纸和订阅者的例子。有一个报纸类Newspaper,有一个订阅者类Subscriber。Subscriber可以订阅Newspaper。这样,当Newspaper有了新的内容的时候,Subscriber可以立即得到通知。在这个例子中,观察者是Subscriber,被观察者是Newspaper。在经典的实现代码中,观察者会将自身注册到被观察者的一个容器中(比如subscriber.registerTo(newspaper))。被观察者发生了任何变化的时候,会主动遍历这个容器,依次通知各个观察者(newspaper.notifyAllSubscribers())。

使用QT信号槽机制来实现上述机制:

//main.cpp
#include 

#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();
}
//Reader.h
#include 
#include 

class Reader : public QObject
{
     
    Q_OBJECT
public:
    Reader() {
     }

    //槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
    void receiveNewspaper(const QString& name)
    {
     
        qDebug() << "Receives Newspaper: " << name;
    }
};
//Newspaper.h
#include 

//只有继承自QObject的类,才有信号槽的能力
class Newspaper : public QObject
{
     
    //凡是Object类,都应在第一行添加Q_Object宏
    //特别注意,这个宏需放到头文件中   
    Q_OBJECT
public:
    Newspaper(const QString& name) :
        m_name(name)
    {
     
    }

    void send()
    {
     
        //emit是QT对c++的扩展,是一个关键字,其实也是一个宏
        //emit的含义:发出newPaper()信号,为了让接收者知道是谁发出的信号,于是将报纸名字作为参数传给这个信号
        //接收者连接该信号时,通过槽函数获取实际值,完成数据从发出者到接收者的一个转移
        emit newPaper(m_name);
    }

//该模块列出信号,信号是一个个函数名,返回值为void,参数是该类想要让外界知道的数据,不需要添加任何实现
signals:
    void newPaper(const QString& name);

private:
    QString m_name;
};

三,Qt模块

Qt5模块分为两部分:
Essentials Modules:所有平台均可使用
Add-on Modules:建立在基础模块的基础上,可选择性引入

详见:Qt模块

四,MainWIndow

QMainWindow是Qt预定义好的主窗口。
主窗口:普通应用程序最顶层的窗口,例如,在使用浏览器时,主窗口就是这个浏览器窗口

vs2019下:

#include 
#include 

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

    QMainWindow win;
    win.show();

    return app.exec();
}   

效果:
Qt学习笔记_第1张图片
虽然看不出来,但据说这个窗口分为这几个部分:
Qt学习笔记_第2张图片

五,对MainWindow添加具体功能

QAction类:代表窗口的动作
该类抽象出公共的动作,在菜单栏和工具栏中使用
包含:图标、菜单文字、快捷键、状态栏文字、浮动帮助等信息

代码:

//mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 

class MainWindow : public QMainWindow
{
     
    Q_OBJECT
public:
    MainWindow(QWidget* parent = 0);
    ~MainWindow();

private:
    void open();

    QAction* openAction;
};

#endif
//mainwindow.cpp
#include 
#include 
#include 
#include 
#include 

#include "mainwindow.h"

//该行说明 MainWindow 的构造函数之前需要调用父类 QMainWindow 的带有参数的构造函数
MainWindow::MainWindow(QWidget* parent) :
    QMainWindow(parent)
{
     
    //设置窗口主标题
    //tr()函数:用于Qt国际化的函数
    setWindowTitle(tr("Main Window"));

    //在堆上创建openAction对象,并对其构造函数传入一个图片,一个文本和this指针

    //QIcon:传入值是一个字符串,对应Qt资源文件中的一个路径,意味着从资源文件中查找资源
    //Qt内置支持的图片格式是png,其它格式的图片入jpg,gif则需要插件支持

    //第二个参数:文本值前有一个&,意味这将成为一个快捷键,注意截图中File有一个下划线
    openAction = new QAction(QIcon(":/images/image1"), tr("&Open..."), this);
    //使用setShortcuts()函数,用于说明QAction的快捷键
    //QKeySequence定义了很多内置的快捷键,比如下面的Open,这样添加快捷键,会根据平台不同来自动定义相应快捷键
    openAction->setShortcuts(QKeySequence::Open);
    //setStatusTi()实现当用户鼠标划过Action时,会在下方状态栏产生的提示
    openAction->setStatusTip(tr("Open an existing file"));
    //将QAction的triggered()信号与MainWindow类的open()连接起来
    connect(openAction, &QAction::triggered, this, &MainWindow::open);

    //menuBar(),toolBar()和statusBar()是三个QMainWindow的内置函数,用于创建窗口的相应部分

    //向菜单栏添加一个FIle菜单,并将openAction添加到这个菜单
    QMenu* file = menuBar()->addMenu(tr("&File"));
    file->addAction(openAction);

    //新增加一个工具栏,将openAction添加到这个菜单
    QToolBar* toolBar = addToolBar(tr("&File"));
    toolBar->addAction(openAction);

    statusBar();
}

MainWindow::~MainWindow()
{
     
}

void MainWindow::open()
{
     
    QMessageBox::information(this, tr("Information"), tr("Open"));
}
//main.cpp
#include 
#include "mainwindow.h"

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

    MainWindow win;
    win.show();

    return app.exec();
}

六,资源管理

参考:
资源管理

七,对象模型

Qt”扩展“了c++:
C++原有对象模型在特定情况下捉襟见肘,不能满足GUI界面要求,为此,在C++编译器编译Qt源程序之前,Qt首先使用moc(Meta Object Compiler)对Qt源代码做一次预处理,此次处理放生在C++预处理器工作之前。
信号函数就是在通过moc处理后才能通过标准C++的编译的。

moc为标准C++增加了一些特性:

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

通过继承QObject类,可以获得这些特性。
更多详见:对象模型

八,布局管理器

组件定位:所谓GUI界面,是一堆组件的叠加,我们应该把组件放在正确的地方, 有两种定位方式可以实现:绝对定位和布局定位。

绝对定位:给出组件坐标和长宽
问题:用户改变窗口大小时,组件不会有相适应的响应

布局定位:由专门的管理器管理,适应调整大小和位置

引入两个新组件:
QSpinBox:只能输入数字,带有上下箭头的步进按钮
QSlider:带滑块的滑杆

布局管理器:

QHBoxLayout:按照水平方向从左到右布局;
QVBoxLayout:按照竖直方向从上到下布局;
QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;
QFormLayout:按照表格布局,每一行前面是一段文本,文本后面跟随一个组件(通常是输入框),类似 HTML 的 form;
QStackedLayout:层叠的布局,允许我们将几个组件按照 Z 轴方向堆叠,可以形成向导那种一页一页的效果。

#include 
#include 
#include 
#include 
#include 
#include 

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);
	//创建实例后,用setRange管理其范围
	spinBox->setRange(0, 130);
	slider->setRange(0, 130);

	QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);
	//若直接用如下写法,编译器会报错:
	//QObject::connect(spinBox, &QSpinBox::valueChanged, slider, &QSlider::setValue);
	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();
}

九,菜单栏、工具栏和状态栏

实际开发中,QMainWindow通常只作为主窗口,对话框更多只使用QDialog类,该类缺少一些方便的函数,如:menuBar()和toolBar()。

QMenuBar:表示菜单的类
addMenu():可以为其添加菜单,可以只提供一个字符串作为参数
&:可以为菜单创建有一个快捷键
addAction():添加活动

QToolBar:表示工具栏的类
addToolBar():

十,对话框

1,对话框简介

对话框:一个顶层窗口,出现在程序最上层,用于实现短期任务或简洁的交互
用QDialog类实现对话框

QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。

//注意parent指针(本例中指的时this)的有无对QDialog实例的影响
void MainWindow::open()
{
     
    QDialog dialog(this);
    dialog.setWindowTitle(tr("Hello,dialog"));
    dialog.exec();
}

Qt支持模态对话框和非模态对话框:
模态对话框:组赛同一应用程序中其它窗口的输入
非模态对话框:

Qt支持两种级别的模态:
应用程序级别和窗口级别
应用程序级别:当该模态对话框出现时,用户必须先与该对话框交互,而不能与应用程序中其它窗口交互。
窗口级别:该模块仅阻塞与该对话框关联的窗口,可以同应用中其它窗口交互

QDialog::exec():实现应用程序级别模态
QDialog::open():实现窗口级别模态
QDialog::show():实现非模态

特别注意:
show()不会阻塞线程,对话框显示,则函数立即返回,代码继续执行
为了解决此问题,应在堆上建立dialog:

void MainWindow::open()
{
     
    QDialog *dialog = new QDialog;
    dialog->setWindowTitle(tr("Hello, dialog!"));
    dialog->show();
}

补充参考:对话框

2,对话框间的数据传递

数据传递:
对话框与主窗口之间的数据交互相当重要

对话框分为模态和非模态两种,两种与主窗口间的数据交互不同

对于使用了exec()显示的模态对话框,我们可以在exec()函数之后直接从对话框的对象获取到数据值。

void MainWindow::open()
{
     
    QDialog dialog(this);
    dialog.setWindowTitle(tr("Hello, dialog!"));
    dialog.exec();
    qDebug() << dialog.result();
}

qDebug()类似于std::cout
exec()开始了一个事件循环,代码被阻塞到这里。由于exec()函数没有返回,因此下面的result()函数也就不会被执行。直到对话框关闭,exec()函数返回,此时,我们就可以取得对话框的数据。

如果我们设置 dialog 的属性为WA_DeleteOnClose,那么当对话框关闭时,对象被销毁,我们就不能使用这种办法获取数据了。在这种情况下,我们可以考虑使用 parent 指针的方式构建对话框,避免设置WA_DeleteOnClose属性;或者是利用另外的方式。

QDialog::exec()是有返回值的,其返回值是QDialog::Accepted或者QDialog::Rejected
常规写法:
根据用户是点击了“确定”还是“取消”来判断

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

非模态对话框,QDialog::show()函数会立即返回,如果我们也这么写,就不可能取得用户输入的数据。
因为show()函数不会阻塞主线程,show()立即返回,用户还没有来得及输入,就要执行后面的代码,当然是不会有正确结果的。

使用信号槽机制:
由于非模态对话框在关闭时可以调用QDialog::accept()或者QDialog::reject()或者更通用的QDialog::done()函数,所以我们可以在这里发出信号。另外,如果找不到合适的信号发出点,我们可以重写QDialog::closeEvent()函数,在这里发出信号。在需要接收数据的窗口(这里是主窗口)连接到这个信号即可。类似的代码片段如下所示:

void UserAgeDialog::accept()
{
     
    emit userAgeChanged(newAge); // newAge is an int
    QDialog::accept();
}

// in main window:
void MainWindow::showUserAgeDialog()
{
     
    UserAgeDialog *dialog = new UserAgeDialog(this);
    connect(dialog, &UserAgeDialog::userAgeChanged, this, &MainWindow::setUserAge);
    dialog->show();
}

// ...

void MainWindow::setUserAge(int age)
{
     
    userAge = age;
}

3,标准对话框QMessageBox

QFileDialog
文件对话框:
QString getOpenFileName() :选择打开一个文件
QStringList getOpenFileNames() :选择打开多个文件
QString getSaveFileName() :选择保存一个文件
QString getExistingDirectory() :选择一个己有的目录
QUrl getOpenFileUrl():选择打幵一个文件,可选择远程网络文件

QcolorDialog
颜色对话框:
QColor getColor():选择颜色

QFontDialog
字体对话框:
QFont getFont():选择字体

QinputDialog
输入对话框:
QString getText():输入单行文字
int getlnt() :输入整数
double getDouble():输入浮点数
QString getltem():从一个下拉列表框中选择输入
QString getMultiLineText(): 输入多行字符串

QMessageBox
消息框:

  • StandardButton information(): 信息提示对话框
  • StandardButton question():询问并获取是否确认的对话框
  • StandardButton waming() :告信息提示对话框
  • StandardButton critical():错误信息提示对话框
  • void about():设置自定义信息的关于对话框
  • void aboutQt():关于Qt的对话框

QMessageBox详解:

if (QMessageBox::Yes == QMessageBox::question(this,
                                              tr("Question"),
                                              tr("Are you OK?"),
                                              QMessageBox::Yes | QMessageBox::No,
                                              QMessageBox::Yes)) {
     
    QMessageBox::information(this, tr("Hmmm..."), tr("I'm glad to hear that!"));
} else {
     
    QMessageBox::information(this, tr("Hmmm..."), tr("I'm sorry!"));
}

QMessageBox类的 static 函数优点是方便使用,缺点也很明显:非常不灵活。我们只能使用简单的几种形式。为了能够定制QMessageBox细节,我们必须使用QMessageBox的属性设置 API。如果我们希望制作一个询问是否保存的对话框,我们可以使用如下的代码:

QMessageBox msgBox;
msgBox.setText(tr("The document has been modified."));
msgBox.setInformativeText(tr("Do you want to save your changes?"));
msgBox.setDetailedText(tr("Differences here..."));
msgBox.setStandardButtons(QMessageBox::Save
                          | QMessageBox::Discard
                          | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec();
switch (ret) {
     
case QMessageBox::Save:
    qDebug() << "Save document!";
    break;
case QMessageBox::Discard:
    qDebug() << "Discard changes!";
    break;
case QMessageBox::Cancel:
    qDebug() << "Close document!";
    break;
}

使用了一个detailedText,也就是详细信息,当点击了详细信息按钮时,对话框可以自动显示更多信息。

深入 Qt5 信号槽新语法

你可能感兴趣的:(qt学习,qt,c++)