目录
指定父对象
在没有给控件对象指定父对象的情况下,窗口之间是独立的,互不相关。为了实现在窗口中包含按钮的效果,需要给按钮控件指定其父对象。
#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对象之间通信的接口。