Qt—对象模型

x33g5p2x  于2022-03-24 转载在 其他  
字(6.2k)|赞(0)|评价(0)|浏览(193)

对象模型

信号与槽

前面已经多次使用过了,这里讲解Qt5新的信号和槽语法,本节内容可以查询Signals & Slots关键字查看

关于信号与槽的关联关系,一个信号可关联到多个槽上,多个信号也可以关联到一个槽上

如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽会一个接一个地执行,执行顺序与关联顺序相同。

下面还是通过例子来进一步理解:
这个例子实现的效果是:在主界面中创建一个对话框,在这个对话框中可以输入数值,当单击“确定”按钮时关闭对话框并且将输入的数值通过信号发射出去,最后在主界面中接收该信号并且显示数值。

新建Qt Widgets应用﹐项目名称为mysignalslot,基类选择QWidget,类名保持 Widget不变。项目建立完成后,向项目中添加新文件,模板选择Qt分类中的“Qt设计师界面类”,界面模板选择Di-alog without Buttons,类名设置为MyDialog。

完成后在mydialog.h文件中添加代码来声明一个信号:

signals:
    void dlgReturn(int);                  // 自定义的信号

整体:

class MyDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyDialog(QWidget *parent = 0);
    ~MyDialog();

private:
    Ui::MyDialog *ui;

signals:
    void dlgReturn(int);                  // 自定义的信号

private slots:
    void on_pushButton_clicked();
};

声明一个信号要使用signal关键字,在signals前面不能用public,private和protected等限定符,因为信号默认是public函数,可以从任何地方进行发射,但是建议只在定义该信号的类及其子类中发射该信号。

信号只用声明,不需要也不能对它进行定义实现。还要注意,信号没有返回值,只能是void类型的。
因为只有QObject类及其子类派生的类才能使用信号和槽机制,这里的 MyDialog类继承自QDialog类,QDia-log类又继承自QWidget类,QWidget类是QObject类的子类,所以这里可以使用信号和槽。
不过,使用信号和槽还必须在类声明的最开始处添加Q_OBJECT宏,在这个程序中,类的声明是自动生成的,已经添加了这个宏。

双击mydialog.ui文件进入设计模式,在界面中添加一个Spin Box部件和一个Push Button部件,将 pushButton的显示文本修改为“确定”。

然后转到pushButton 的单击信号clicked()对应的槽,更改如下:

void MyDialog::on_pushButton_clicked() //确认按钮
{
    int value = ui->spinBox->value();    // 获取输入的数值
    emit dlgReturn(value);               // 发射信号
    close();                             // 关闭对话框
}

这样点击确认按钮获取值,发射dlgReturn信号

然后到widget.h文件中添加自定义槽的声明:

private slots:
    void showValue(int value);

槽就是普通的C++函数,可以像一般的函数一样使用。声明槽要使用slots关键字,一个槽可以是 private , public或者 protected类型的,槽也可以被声明为虚函数﹐这与普通的成员函数是一样的。槽的最大特点就是可以和信号关联

下面打开widget.ui文件,向界面上拖入一个Label部件,更改其文本为“获取的值是:”。

然后进入 widget.cpp文件添加头文件#include“mydialog.h”,再在构造函数中添加代码:

MyDialog *dlg = new MyDialog(this);
   // 将对话框中的自定义信号与主界面中的自定义槽进行关联
 connect(dlg, SIGNAL(dlgReturn(int)), this, SLOT(showValue(int)));
   dlg->show();

这里创建了一个MyDialog实例dlg,并且使用widget作为父部件。然后将MyDialog类的dlgReturn()信号与showValue槽进行关联

下面添加showValue槽的实现

void Widget::showValue(int value)         // 自定义槽
{
    ui->label->setText(tr("获取的值是:%1").arg(value));
}

这里将参数上的vlaue传递显示到标签上label
这里解释下qt的string表示法

ui->label->setText(tr(“获取的值是:%1”).arg(value));
%1.arg(value)
最后输出value
QT学习之QString的arg方法

按下按键,发射信号,连接槽,实现槽函数

这里列举一下使用信号与槽需要注意的几点:

  1. 需要继承自QObject或其子类;
    2.在类声明的最开始处添加Q_OBJECT宏;
    3.槽中参数的类型要和信号参数的类型相对应,且不能比信号的参数多;
    4.信号只用声明,没有定义,且返回值为void类型。

信号与槽的关联

connect函数
函数原型:

[static]QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, 
const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

第一个参数为发射信号的对象,例如这里的 dlg;
第二个参数是要发射的信号,这里是SIGNAL(dlgReturn(int));
第三个参数是接收信号的对象,这里是 this,表明是本部件,即 Widget,当这个参数为this时,也可以将这个参数省略掉,因为connect()函数还有另外一个重载形式,该参数默认为 this;
第四个参数是要执行的槽,这里是SLOT(showValue(int)),其实该参数也可以指定一个信号,实现信号与信号的关联。
对于信号和槽,必须使用SIGNAL()和 SLOT()宏,它们可以将其参数转化为const char*类型,另外,第四个参数指定的槽在声明时必须使用slots关键字。

connect()函数的返回值为QMetaObject : : Connection类型,该返回值可以用于QObject : ; disconnect( constQMetaObject : : Connection &.connection)函数来断开该关联。

注意,在调用该connect()函数时信号和槽的参数只能有类型,不能有变量名,如写成SLOT(showValue(int value))是不对的。(有变量value不对)

对于信号和槽的参数问题,基本原则是信号中的参数类型要和槽中的参数类型相对应,而且信号中的参数可以多于槽中的参数,但是不能反过来,如果信号中有多余的参数,那么它们将被忽略。

connect()函数的最后一个参数 type表明了关联的方式,由Qt: : ConnectionType枚举类型指定,其默认值是Qt::AutoConnec-tion,这里还有其他几个选择,具体功能如表所列。

编程中一般使用默认值,例如,这里在MyDialog类中使用emit发射了信号之后,就会立即执行槽,只有等槽执行完了以后,才会执行emit语句后面的代码。
这里可以将这个参数改为Qt : :QueuedConnec-tion测试下效果,这样在执行完emit语句后便会立即执行其后面的代码,而不管槽是否已经执行。

connect函数另一个常用的基于函数指针的重载形式如下:

[static] QMetaObject::Connection Q0bject:: connect(const Q0bject * sender,
PointerToMemberFunction signal,const Q0bject * receiver,
PointerToMemberFunction method,
Qt:: ConnectionType type = Qt:: AutoConnection)

这是Qt 5中加入的一种重载形式,与前者最大的不同就是,指定信号和槽两个参数时不用再使用SIGNAL()和SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,而可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型与信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。使用这种重载形式,前面程序中的关联可以使用如下代码代替:

connect(dlg,&MyDialog::dlgReturn,this,&Widget::showValue);

这样就不用使用slot和signal,参数也可以转换类型

使用这种方式与前一种相比,还有一个好处就是可以在编译时进行检查,信号或槽的拼写错误,槽函数参数数目多于信号的参数数目等错误在编译时就能够被发现。

所以建议在编写Qt 5代码时使用这种关联形式,本书中的示例程序一般也使用这种关联形式。另外,这种形式还支持C++11中的Lambda 表达式,可以在关联时直接编写信号发射后要执行的代码,例如,程序中的关联可以写为:

connect(dlg,&MyDialog::digReturn,[=](int value){
       ui->label->setText(tr("获取的值是:%1").arg(value));
    })

这样就不需要定义槽函数了。

例如:例如,一个Lambda表达式需要两个参数,以传值方式捕获的a和以传引用方式捕获的str,其返回值是QString类型,那么这个Lambda表达式应该写成:

[a,&str]->QString{}

如果需要了解有关Lambda表达式的更多信息,可参考《C++ Primer(第5版)》或其他相关教程。
前面提到,对于带有默认参数的槽函数,可以使用Lambda表达式作为一个匿名函数,从而达到函数封装的目的。依旧以前面提到的函数为例,可以使用下面的代码重写:
connect(myWidget, &Mywidget: : aSingal,
[]{ pushButton->anumateClick()});

信号与槽的自动关联

信号与槽可以用ui界面的自动关联
下面讲解一个简单的例子:
新建Qt widget应用,项目名称mysignalslot2,基类选择Qwidget。

完成后先在widget.h文件中进行槽函数声明:

private slots:
    void on_myButton_clicked();

这里自定义一个槽,它使用自动关联。然后在widget.cpp中添加头文件#include< QPushButton>,再将构造函数的内容更改如下:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    QPushButton *button = new QPushButton(this); // 创建按钮
    button->setObjectName("myButton");           // 指定按钮的对象名
    ui->setupUi(this);                      // 要在定义了部件以后再调用这个函数
}

因为setupUi()函数中调用了connectSlotsByName()函数,所以要使用自动关联的部件的定义都要放在setupUi()函数调用之前,而且还必须使用setObjectName()指定它们的objectName,只有这样才能正常使用自动关联。

下面添加槽的定义:

void Widget::on_myButton_clicked()          // 使用自动关联
{
    close();
}

这里进行了关闭部件的操作。对于槽的函数名,中间要使用前面指定的 object-Name,这里是myButton。现在运行程序,单击按钮,发现可以正常关闭窗口。

可以看到,如果要使用信号和槽的自动关联,就必须在connectSlotsByName()函数之前进行部件的定义,而且还要指定部件的objectName。鉴于这些约束,虽然自动关联形式上很简单,但是实际编写代码时却很少使用。而且,定义一个部件时很希望明确地使用connect()函数来对其进行信号和槽的关联,这样当其他开发者看到这个部件定义时,就可以知道和它相关的信号和槽的关联了,而使用自动关联却没有这么明了。

断开关联

可以通过disconnect()函数来断开信号和槽的关联,其原型如下:

[static] bool Q0bject ;disconnect(const QObject * 
sender, const char * signal, const Q0bject * receiver,const char * method)

该函数一般有下面几种用法:
①断开与一个对象所有信号的所有关联:

disconnect(myObject,0,0,0);

等价于:

myObject->disconnect();

②断开与一个指定信号的所有关联:

disconnect(myObject,SIGNAL(mySignal(),0,0);

等价于:

myObject->disconnect(SIGNAL(mySignal()));

③断开与一个指定的receiver的所有关联:

disconnect(myObject,0,myReceiver,O);

等价于:

myObject->disconnect(myReceiver);

④断开一个指定信号和槽的关联:

disconnect(myobject,SIGNAL(mySignal()),myReceiver,SLOT(mySlot()));

等价于:

myObject->disconnect(SIGNAL(mySignal()),myReceiver,SLOT(mySlot());

也等价于:

disconnect(myConnection);//myConnection是进行关联时connect()的返回值

与connect()函数一样,disconnect()函数也有基于函数指针的重载形式:

[static ] bool Q0bject:;disconnect(const QO0bject * sender,PointerToMemberFunction signal,const Q0bject * receiver,PointerToMemberFunction method)

其用法类似,只是其信号,槽参数需要使用函数指针&MyObject : : mySignal(),&.MyReceiver : : mySlot()等形式。这个函数并不能断开信号与一般函数或者lambda表达式之间的关联,如果有这方面需要,则可以使用connect()返回值进行断开。

信号与槽的高级应用

影响大众想象力的,并不是事实本身,而是它扩散和传播的方式。

相关文章