当相同的信号可以来自多个地方时,使用Qt的信号和插槽

时间:2015-08-13 18:30:46

标签: c++ qt

当您在一个组件中发生事件并且需要由一个或多个其他组件处理时,Qt的信号和插槽机制可以正常工作。

我的情况是,一个事件可以在两个类中的任何一个中发生,并且需要由每个类(以及其他几个类)处理。例如,假设我正在编写模态文本编辑器。用户可以更改模式(通过按工具栏上的按钮)或应用程序(打开新文件时)。我可能有

// Toolbar.h

signals:
    void user_changed_mode(EditingMode new_mode);
    // connected to AppController::user_changed_mode

public slots:
    void mode_changed(EditingMode new_mode);

// AppController.h

signals:
    void mode_changed(EditingMode new_mode);
    // connected to Toolbar::mode_changed

public slots:
    void user_changed_mode(EditingMode new_mode);

对于我来说,传达相同信息但具有不同名称的两个信号(同样适用于插槽)对我来说似乎很尴尬。当同一事件可以来自多个地方时,是否有一种简单的方法来使用信号和插槽机制?

2 个答案:

答案 0 :(得分:1)

您可以将多个信号连接到同一个插槽。在应用程序初始化的某处,您只需将每个对象的信号连接到同一个插槽:

connect(object1, SIGNAL(object1_changed_mode(EditingMode)),
        receiverInAppController, SLOT (mode_changed(EditingMode)) );

connect(object2r, SIGNAL(object2_changed_mode(EditingMode)),
        receiverInAppController, SLOT(mode_changed(EditingMode)) );

另请注意,Qt5有另一种simplified syntax。不过,Qt5仍与上述符号兼容。

最后,信号也可以具有相同的名称。唯一的要求是每个信号在每个对象的类定义中单独声明。

答案 1 :(得分:1)

请记住,信号和插槽可以具有任何有效C ++标识符的名称,并且它们的范围是您声明它们的类。

因此,只要这些名称有意义且没有误导性,多个类中的信号和槽可以具有相同的名称。

但是,还有另一个问题。您很可能有一个更改循环,并且您的代码将因无限递归而崩溃。当工具栏的模式发生变化时,它必须发出mode_changed信号,否则你将打破代码应该具有的典型语义。因此,假设控制器发出模式改变信号,然后工具栏接收它,更改模式,并发出确认信号,控制器执行相同操作,因此它将永远消失。

打破这种循环的方法是将第一次调用时触发的信号与随后作为这种变化的结果发出的信号区分开来。您可以使用区分布尔值,或重新使用Qt::ItemDataRoleQt::EditRole作为更改源,Qt::DisplayRole表示更改的所有后续指示 - 这是怎样的你break the property binding loops when using QML and models

因此:

enum class EditingMode { ... };
Q_DECLARE_METATYPE(EditingMode)

class Toolbar : public ... {
  Q_OBJECT
public:
  Q_SIGNAL void modeChanged(EditingMode, Qt::ItemDataRole role = Qt::DisplayRole);
  Q_SLOT void setMode(EditingMode new_mode, Qt::ItemDataRole role = Qt::EditRole) {
    ... // perform mode changes
    if (role == Qt::EditRole) emit modeChanged(new_mode);
  }
  ...
};

class AppController : public ... {
  Q_OBJECT
public:
  Q_SIGNAL void modeChanged(EditingMode, Qt::ItemDataRole role = Qt::DisplayRole);
  Q_SLOT void setMode(EditingMode new_mode, Qt::ItemDataRole role = Qt::EditRole) {
    ... // perform mode changes
    if (role == Qt::EditRole) emit modeChanged(new_mode);
  }
  ...
};

int main(int argc, char ** argv) {
  ...
  Toolbar toolbar1, toolbar2;
  AppController controller;
  for (auto toolbar : QList<Toolbar*>() << &toolbar1 << &toolbar2) {
    QObject::connect(toolbar, &Toolbar::modeChanged, &controller, &Controller::setMode);
    QObject::connect(&controller, &Controller::modeChanged, toolbar, &Toolbar::setMode);
  }
  ...
  toolbar1.setMode(Mode1);
  // toolbar2 gets notified, but doesn't notify anyone else again
  // controller gets notified, but doesn't notify anyone else again
  ...
  controller.setMode(Mode2);
  // toolbar1 gets notified, but doesn't notify the controller again
  // toolbar2 gets notified, but doesn't notify the controller again
}

不幸的是,Qt自己的Widget模块控件并没有遵循这样的模式,当您尝试链接多个控件以便彼此跟随时,会出现欢闹...有些提供的特殊信号只会被发出当用户修改数据时,但并非所有人都这样做,并且额外的信号更难处理......

当你使用Qt Quick时,只要你遵循这个或类似的模式,一切都很好。