Qt学习(二)—— 信号和槽

目录

指定父对象

标准信号和槽(动作和处理函数)

自定义槽函数

实验:实现两个独立窗口

信号重载

总结


指定父对象

在没有给控件对象指定父对象的情况下,窗口之间是独立的,互不相关。为了实现在窗口中包含按钮的效果,需要给按钮控件指定其父对象。

#include <QApplication>
#include <QWidget>    //窗口控件头文件
#include <QPushButton>    //按钮控件头文件
int main(int argc,char **argv){
    QApplication app(argc,argv);
    QWidget w;
    QPushButton b;
    
    w.setWindowTitle("helloqt");    //设置窗口标题
    w.show();   //人为使窗口显示
    
    b.setText("我是按钮");  //设置按钮内容
    b.show();   //人为使按钮显示
    
    app.exec();
    return 0;
}

指定父对象的方式有两种:①setParent() ②通过构造函数传参

指定父对象,只需要父对象显示,上面的子对象由于包含在父对象中,也会自动显示

注意:父控件要在子控件定义并设置好相关内容后再调用show()才能将子控件也显示出来,否则子控件不会显示。

//调用setParent()指定父对象
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc,char **argv){
    QApplication app(argc,argv);
    QWidget w;
    QPushButton b;

    w.setWindowTitle("helloqt");    //设置窗口标题
    //w.show()写在这里子控件不会显示!!

    b.setText("我是按钮");  //设置按钮内容
    b.setParent(&w);    //参数类型是指针,因此要带地址
    b.move(100,100);

    w.show();   //人为使窗口显示

    app.exec();
    return 0;
}
//用构造函数指定父对象
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc,char **argv){
    QApplication app(argc,argv);
    QWidget w;

    w.setWindowTitle("helloqt");    //设置窗口标题
    //w.show()写在这里子控件不会显示!!

    QPushButton b1(&w);
    b1.setText("构造函数指定父对象");

    w.show();   //人为使窗口显示

    app.exec();
    return 0;
}

标准信号和槽(动作和处理函数)

为了不使main.cpp中的代码过于冗长,也为了更好地设计符合我们需要的自定义控件,对于父控件中的子控件的定义可以写在父控件对应的头文件中,在main.cpp引入头文件即可。

为什么子控件的定义不写在父控件的构造函数中呢?因为在main.cpp中,程序是顺序执行的,执行完父控件的构造函数后,构造函数中定义的子控件就不存在了。父控件调用show()方法时也就不会显示子控件。而对控件的操作可以写在构造函数中。

下面是直接在父控件构造函数中对子控件进行定义和操作的代码,运行结果是只显示父控件,不显示子控件。

//父控件构造函数 mainwidget.cpp
#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
}

MainWidget::~MainWidget()
{

}

注意:执行完父控件的构造函数后,定义在父控件构造函数中的子控件就不复存在了,因此父控件中不会显示子控件。

将子控件声明在父控件对应的头文件中,在父控件的构造函数中对子控件进行具体的操作。运行结果是父控件中包含两个子控件。

//父控件的头文件 mainwidget.h
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();
private:
    QPushButton b1;
    QPushButton *b2;
};

#endif // MAINWIDGET_H
====================================================================
//父控件的构造函数 mainwidget.cpp
#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("我是按钮1");
    b1.move(100,100);

    b2=new QPushButton(this);    //申请空间并利用构造函数指定父元素
    b2->setText("我是按钮2");
}

MainWidget::~MainWidget()
{

}

注意:由于b2定义时是指针类型,因此要使用它之前必须要用new关键字申请一个新的空间。调用方法时不用.而用->。

Qt中用一个connect()方法来建立父控件与子控件之间的联系。用法是:

connect(sender, SIGNAL(signal), receiver, SLOT(slot));

其中,sender是信号发送者,SIGNAL(signal)是处理的信号,receiver是信号接收者,SLOT(slot)是信号处理函数

#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
/*    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
*/
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("点我关闭");
    b1.move(100,100);
    b2=new QPushButton(this);
    b2->setText("我是按钮2");

    connect(&b1,&QPushButton::pressed,this,&MainWidget::close);
    /*
        &b1:信号发出者,指针类型
        &QPushButton::pressed:处理信号,&发送者的类名::信号名字
        this:信号接收者
        &MainWidget::close:槽函数,信号处理函数,&接收者的类名::槽函数名字
    */
}

MainWidget::~MainWidget()
{

}

注意:

处理信号有哪些?可以将鼠标移至头文件处,按F1,在帮助文档右侧的Content中查找Signals,如果找不到,那可能是在当前类所继承的基类中。

 

因此,我们可以知道QPushButton类的信号可以有clicked、pressed、released、toggled这几种。 

处理信号和槽函数作为connect()的参数时,要写明发送者类名::信号名字和接受者类名::槽函数名字,并且要带上取地址符。

自定义槽函数

如果自带的槽函数不足以实现我们想要的处理效果,还可以自定义槽函数。 

那么哪些函数可以作为槽函数呢?任意成员函数、普通全局函数、静态函数

自定义槽函数的用法与普通函数用法无异,槽函数需要和信号一致(参数、返回值),由于信号是没有返回值的,所以槽函数也没有。

一般都把槽函数作为信号接收者所属类的成员函数,因此在接收者所属类的头文件中声明,在接收者所属类的构造函数中去实现。在帮助文档中,信号的参数和返回值是什么,对应的槽函数的参数和返回值就是什么。

//mainwidget.h 窗口的头文件
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();
    void MySlot();    //声明自定义槽函数,以成员函数的形式存在
private:
    QPushButton b1;
    QPushButton *b2;
};

#endif // MAINWIDGET_H
========================================================================
//mainwidget.cpp 窗口的构造函数
#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
/*    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
*/
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("点我关闭");
    b1.move(100,100);
    b2=new QPushButton(this);
    b2->setText("我是按钮2");

    connect(&b1,&QPushButton::pressed,this,&MainWidget::close);
    /*
        &b1:信号发出者,指针类型
        &QPushButton::pressed:处理信号,&发送者的类名::信号名字
        this:信号接收者
        &MainWidget::close:槽函数,信号处理函数,&接收的类名::槽函数名字
    */
    connect(b2,&QPushButton::released,this,&MainWidget::MySlot);
    connect(b2,&QPushButton::released,&b1,&QPushButton::hide);
    /*
        信号:短信
        槽函数:接收短信的手机
        
    */
}
void MainWidget::MySlot(){
    b2->setText("槽函数之成员函数");
}
MainWidget::~MainWidget()
{

}

注意:

b2在头文件中定义时就是一个指针类型,因此作为参数时不需要再用取地址符。

槽函数的实现写在构造函数中。

实验:实现两个独立窗口

下面我们要实现两个独立窗口,点击主窗口中的按钮时,主窗口隐藏,显示副窗口,点击副窗口中的按钮时,副窗口隐藏,显示主窗口。

创建副窗口时,在工程下新建一个C++ Class文件。Base Class基类是QWidget,Class Name假设是SubWidget。

//subwidget.h 副窗口头文件
#ifndef SUBWIDGET_H
#define SUBWIDGET_H

#include <QWidget>
#include <QPushButton>
class SubWidget : public QWidget
{
    Q_OBJECT
public:
    explicit SubWidget(QWidget *parent = 0);

signals:

public slots:

private:
    QPushButton b;

};

#endif // SUBWIDGET_H
====================================================================
//subwidget.cpp 副窗口构造函数
#include "subwidget.h"

SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle("小弟");
    b.setText("点击切换到主窗口");
    b.setParent(this);
}


这时候会发现只显示出主窗口,为什么?

因为在main.cpp中,只对mainwidget进行显示,subwidget是独立于mainwidget的,它就不会显示。

此外,因为指定父对象之后会在另一个窗口里面,为了让两个窗口独立,也不能指定父子关系。而应该通过另一个窗口来控制当前窗口是否显示。

因此我们在主窗口的头文件中引入副窗口的头文件,为副窗口创建一个对象。将副窗口对象作为主窗口构造函数中的一个私有成员。想要让副窗口显示出来,只需要在主窗口的构造函数中对副窗口对象调用show()方法。

//mainwidget.h 主窗口头文件
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
#include "subwidget.h"    //引入副窗口头文件,非标准库用的是""
class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();
    void MySlot();
private:
    QPushButton b1;
    QPushButton *b2;
    QPushButton b3;
    SubWidget w;    //创建一个副窗口对象,作为主窗口的私有成员
};

#endif // MAINWIDGET_H
================================================================
//mainwidget.cpp 主窗口构造函数
#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
/*    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
*/
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("点我关闭");
    b1.move(100,100);
    b2=new QPushButton(this);
    b2->setText("我是按钮2");

    connect(&b1,&QPushButton::pressed,this,&MainWidget::close);
    /*
        &b1:信号发出者,指针类型
        &QPushButton::pressed:处理信号,&发送者的类名::信号名字
        this:信号接收者
        &MainWidget::close:槽函数,信号处理函数,&接收的类名::槽函数名字
    */
    connect(b2,&QPushButton::released,this,&MainWidget::MySlot);
    connect(b2,&QPushButton::released,&b1,&QPushButton::hide);
    /*
        信号:短信
        槽函数:接收短信的手机

    */
    this->setWindowTitle("老大");
    b3.setText("点击切换到副窗口");
    b3.setParent(this);
    b3.move(50,50);
    w.show();    //在主窗口的构造函数中控制副窗口的显示
}
void MainWidget::MySlot(){
    b2->setText("槽函数之成员函数");
}
MainWidget::~MainWidget()
{

}

因为子窗口和副窗口没有用setParent来指定父子关系,因此它们是相互独立的。

在主窗口中通过按钮控制自身的隐藏自己副窗口的显示很容易实现,但是在副窗口如何通过按钮来实现自身的隐藏和主窗口的显示呢?

因为副窗口没有权限处理主窗口,只好发送一个信号让主窗口自己来处理。其实就是副窗口发出一个信号,由主窗口接收并决定如何处理。

在副窗口的头文件中,声明一些信号,声明信号必须用signals关键字。

信号的使用就是在函数调用的基础上加上emit关键字。如 emit mySignal();

//mainwidget.h 主窗口头文件
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
#include "subwidget.h"
class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();
    void MySlot();
    void hideBoss();
    void hideBro();
private:
    QPushButton b1;
    QPushButton *b2;
    QPushButton b3;
    SubWidget w;
};

#endif // MAINWIDGET_H
===========================================================
//mainwidget.cpp 主窗口构造函数
#include "mainwidget.h"
#include <QPushButton>
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
/*    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
*/
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("点我关闭");
    b1.move(100,100);
    b2=new QPushButton(this);
    b2->setText("我是按钮2");
    resize(400,300);

    connect(&b1,&QPushButton::pressed,this,&MainWidget::close);
    /*
        &b1:信号发出者,指针类型
        &QPushButton::pressed:处理信号,&发送者的类名::信号名字
        this:信号接收者
        &MainWidget::close:槽函数,信号处理函数,&接收的类名::槽函数名字
    */
    connect(b2,&QPushButton::released,this,&MainWidget::MySlot);
    connect(b2,&QPushButton::released,&b1,&QPushButton::hide);
    /*
        信号:短信
        槽函数:接收短信的手机

    */
    this->setWindowTitle("老大");
    b3.setText("点击切换到副窗口");
    b3.setParent(this);
    b3.move(50,50);
  //  w.show();

    connect(&b3,&QPushButton::released,this,&MainWidget::hideBoss);
    connect(&w,&SubWidget::mySignal,this,&MainWidget::hideBro);    //主窗口收到副窗口发来的mySignal信号
}
void MainWidget::MySlot(){
    b2->setText("槽函数之成员函数");
}
void MainWidget::hideBoss(){
    this->hide();
    w.show();
}
void MainWidget::hideBro(){
    show();
    w.hide();
}

MainWidget::~MainWidget()
{

}
==============================================================
//subwidget.h 副窗口头文件
#ifndef SUBWIDGET_H
#define SUBWIDGET_H

#include <QWidget>
#include <QPushButton>
class SubWidget : public QWidget
{
    Q_OBJECT
public:
    explicit SubWidget(QWidget *parent = 0);
    void sendSlot();    //这是常函数

signals:
    /*
        信号必须有signals关键字来声明
        信号没有返回值,但可以有参数
        信号就是函数的声明,只需声明,无需定义
        使用:emit mySignal();
    */
    void mySignal();    //这是信号

public slots:

private:
    QPushButton b;

};

#endif // SUBWIDGET_H
============================================================
//subwidget.cpp 副窗口构造函数
#include "subwidget.h"

SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle("小弟");
    b.setText("点击切换到主窗口");
    b.setParent(this);
    resize(400,300);
    connect(&b,&QPushButton::released,this,&SubWidget::sendSlot);
}
void SubWidget::sendSlot()
{
    emit mySignal();
}

从以上代码可以看到,为实现两个独立窗口,不能为这两个窗口指定父子关系,但又要实现两个窗口之间的互相控制。主窗口对副窗口进行控制很简单,只需要将副窗口作为自己构造函数中的私有成员,调用connect(),利用槽函数实现具体操作,槽函数在头文件中声明,在构造函数中实现。而副窗口没有权限对主窗口进行操作,因此需要用到信号。对于副窗口,调用connect(),利用槽函数发送信号。同时,在主窗口的构造函数中调用connect(),接收由副窗口发送过来的信号,并利用槽函数对主窗口进行操作。这样间接实现了副窗口对主窗口的控制。

实现效果:

执行项目,效果如下

点击“我是按钮2” ,效果如下

点击“点击切换到副窗口”按钮,效果如下 

注意:在Qt中运行别人的项目时,要先删掉项目目录中的.pro.user文件,否则项目的编译器路径是别人设置的路径,和本机的路径往往不一致,编译可能会不成功。

信号重载

信号可以重载(同名但参数不同)。信号是给槽函数传参的,所以信号的参数是什么类型,槽函数中对应的参数就应该是什么类型。

//subwidget.h
#ifndef SUBWIDGET_H
#define SUBWIDGET_H

#include <QWidget>
#include <QPushButton>
class SubWidget : public QWidget
{
    Q_OBJECT
public:
    explicit SubWidget(QWidget *parent = 0);
    void sendSlot();    //这是常函数

signals:
    /*
        信号必须有signals关键字来声明
        信号没有返回值,但可以有参数
        信号就是函数的声明,只需声明,无需定义
        使用:emit mySignal();
    */
    void mySignal();    //这是信号
    void mySignal(int,QString);    //带参数的信号声明

public slots:

private:
    QPushButton b;

};

#endif // SUBWIDGET_H
==============================================================
//subwidget.cpp
#include "subwidget.h"

SubWidget::SubWidget(QWidget *parent) : QWidget(parent)
{
    this->setWindowTitle("小弟");
    b.setText("点击切换到主窗口");
    b.setParent(this);
    resize(400,300);
    connect(&b,&QPushButton::released,this,&SubWidget::sendSlot);
}
void SubWidget::sendSlot()
{
    emit mySignal();
    emit mySignal(250,"哈哈哈");
}
==============================================================
//mainwidget.h
#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include <QWidget>
#include <QPushButton>
#include "subwidget.h"
class MainWidget : public QWidget
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = 0);
    ~MainWidget();
    void MySlot();
    void hideBoss();
    void hideBro();
    void outPut(int,QString);   //与信号参数一致
private:
    QPushButton b1;
    QPushButton *b2;
    QPushButton b3;
    SubWidget w;
};

#endif // MAINWIDGET_H
==============================================================
//mainwidget.cpp
#include "mainwidget.h"
#include <QPushButton>
#include <QDebug>   //打印
MainWidget::MainWidget(QWidget *parent)
    : QWidget(parent)
{
/*    QPushButton b;
    b.setParent(this);
    b.setText("构造函数中定义子控件");
*/
    //在头文件mainwidget.h中已经定义过按钮b1,b2了,无需再定义
    b1.setParent(this);
    b1.setText("点我关闭");
    b1.move(100,100);
    b2=new QPushButton(this);
    b2->setText("我是按钮2");
    resize(400,300);

    connect(&b1,&QPushButton::pressed,this,&MainWidget::close);
    /*
        &b1:信号发出者,指针类型
        &QPushButton::pressed:处理信号,&发送者的类名::信号名字
        this:信号接收者
        &MainWidget::close:槽函数,信号处理函数,&接收的类名::槽函数名字
    */
    connect(b2,&QPushButton::released,this,&MainWidget::MySlot);
    connect(b2,&QPushButton::released,&b1,&QPushButton::hide);
    /*
        信号:短信
        槽函数:接收短信的手机

    */
    this->setWindowTitle("老大");
    b3.setText("点击切换到副窗口");
    b3.setParent(this);
    b3.move(50,50);
  //  w.show();

    connect(&b3,&QPushButton::released,this,&MainWidget::hideBoss);
    connect(&w,&SubWidget::mySignal,this,&MainWidget::hideBro);
    connect(&w,&SubWidget::mySignal,this,&MainWidget::outPut);
    //上面两个信号同名,产生二义性,编译时会出错
}
void MainWidget::MySlot(){
    b2->setText("槽函数之成员函数");
}
void MainWidget::hideBoss(){
    this->hide();
    w.show();
}
void MainWidget::hideBro(){
    show();
    w.hide();
}
void MainWidget::outPut(int a,QString b){    //带参数的槽函数的具体实现
    qDebug()<<a<<b;    //用法类似于c++中的cout
}

MainWidget::~MainWidget()
{

}

信号重载时会产生二义性,因此需要用到函数指针。

下面这种做法相当于是给两个同名函数起了不一样的名字。

void (SubWidget::*signalWithout)()=&SubWidget::mySignal;
connect(&w,signalWithout,this,&MainWidget::hideBro);
void (SubWidget::*signalWith)(int,QString)=&SubWidget::mySignal;
connect(&w,signalWith,this,&MainWidget::outPut);

注意:函数指针前要指明作用域,由于是指针,作为connect参数时不需要再加&符。

此时虽然编译通过且有运行结果,但中文字符呈现的是Unicode编码,要用toUtf8().data()转化成utf-8编码。

其中,toUtf8()将字符串转化为字节数组,data()将字节数组转化为char *。

void MainWidget::outPut(int a,QString b){
    qDebug()<<a<<b.toUtf8().data();
}

下面我们来看看,Qt4是怎样实现信号连接的。

这里用到的槽函数必须在头文件中手动用slots关键字对其进行修饰。

//mainwidget.cpp
connect(&subWin,SIGNAL(mySignal(int,QString)),this,SLOT(outPut));
//mainwidget.h
public slots:
    void outPut(int,QString);

Qt4的缺点主要有两个,一是槽函数一定要用slots修饰,二是SIGNAL和SLOT是宏,会将函数名字转换成字符串,这样就不会进行错误检查,即使函数名写错仍能通过编译。

Qt5的缺点是当有信号重载时需要用不同的函数指针接收。

总结

※ 创建项目时,可供选择的基类有MainWindow、QWidget、QDialog,它们分别是带菜单栏的窗口、普通窗口、不可伸缩的对话框,当我们指定Class name并创建项目后,在该类的.h文件中会自动生成class ClassName:public BaseClass{...}。也就是说所创建的类是公有继承自我们所选择的基类的。

※ 指定父对象有两种方式,第一种是通过setParent(),第二种是通过构造函数传参。指定父对象可以实现内嵌效果,这样一来只需要对父控件调用show(),子控件也会一并显示出来。否则,控件之间是相互独立的,需要手动对所有想要显示的控件调用show()。

※ 实现两个独立窗口,不仅可以将副窗口作为主窗口构造函数的私有成员,也可以直接在main.cpp文件中分别引入主、副窗口的头文件,并分别声明一个主、副窗口对象,再分别对它们调用show()方法。

※ 信号与槽是Qt对象之间通信的接口。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值