将参数绑定到信号/槽函数

10

我有多个事件信号,我想将它们连接到同一个槽中。我想知道的是如何向该槽传递基于字符串的参数,以便槽知道这个信号来自哪里。一种替代方法是根据信号数量制作同样数量的槽,然后以1:1方式将它们连接起来,但考虑到所有处理的代码非常相似,这种方法效率低下。我尝试过这样做,但出现了一些错误:

connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));
connect(button1,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button1")));
connect(button2,SIGNAL(clicked()),this,SLOT(backgroundTypeChoiceMade("button2")));

这个错误与我在最后两个命令中传递的参数有关.. 而backgroundTypeChoiceMade是这样声明的:

void backgroundTypeChoiceMade(QString);

有人能告诉我上面的代码中错误在哪里吗?

6个回答

9
你可以使用QSignalMapper。虽然QSignalMapper是你问题的答案,但我认为Jon Hanson的回答才是你应该采取的方法。这样你可以得到更加简洁的代码。

我认为在这种情况下,QSignalMapper 是最好的选择。想象一下将按钮更改为 QComboBox 或任何其他 UI 更改。使用 QSignalMapper,在 .cpp 文件中进行微小更改即可。而使用单独的 slots,则需要更改类签名。 - hyde

8

四种方法,其中一种不错。

  1. QSignalMapper。有效,但代码混乱。
  2. 命名槽。对于任何大量发送者的情况都很混乱,并且无法处理动态生成的发送者(例如列表中的按钮)。
  3. sender()-compare。可以处理动态发送者,但仍然有点丑陋。
  4. 子类化发送者。不错。让你真正想要的得到了: 参数化信号

特别是当你使用少量的信号和发送者类型,并且发送者是动态生成的时,子类化发送者是最干净的方式。这使你能够重载现有的信号以包含任何需要的参数。

现在,只需进行信号和插槽的连接即可:完美工作:

Keypad::Keypad(QWidget *parent) : QWidget(parent)
{
    for (int i = 0; i < 10; ++i)
    {
        // KeypadButton keeps track of the identifier you give it
        buttons[i] = new KeypadButton(i, this);
        // And passes it as a signal parameter. Booyah.
        connect(buttons[i], SIGNAL(clicked(int)), this, SIGNAL(digitClicked(int)));
    }
    createLayout();
}

void Keypad::digitClicked(int digit)
{
    // The slot can find the clicked button with ease:
    dial(button[i]); // or whatever
    //...
}

并且额外的代码在子类中是隐藏的,你再也不必触及它了。
参见http://doc.qt.digia.com/qq/qq10-signalmapper.html#thesubclassapproach,其中有一个将QPushButton作为子类以发出clicked(int)信号的示例实现。还讨论了所有四种方法-命名槽(“平凡的解决方案”)、发件人(sender())、子类化和信号映射器。
注意:显然最适用于少量的发件人类型。但通常情况下都是如此。在这种情况下,这样做是值得的。

1
我认为你应该尽可能避免使用QSignalMapper。最好的解决方案(可以使代码更清晰)是: 1)像@Foamy所写的那样,子类化并在信号中发送所需的ID。我们在Qt项目中尽可能使用这种方法(但不建议仅为此目的而子类化)2)在槽函数中获取sender(),进行类型检查并与您想要的任何ID /标记和sender()具有的进行比较。 - Viktor Benei
@ViktorBenei 我同意,除了我认为仅为参数化信号而子类化是值得的。在子类化中需要更多的工作,但可以使您的其余代码更加清晰。 - Nolan Amy

5
使用单独的插槽有什么效率低下的地方?如果插槽处理程序中存在共性,则将其移动到一个函数中,例如扩展ereOn的示例:
void YourClass::YourClass() :
  m_button1(new QPushButton()),
  m_button2(new QPushButton())
{
  connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot1()));
  connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot2()));
}

void YourClass::common(int n)
{
}

void YourClass::yourSlot1()
{
    common (1);
}

void YourClass::yourSlot2()
{
    common (2);
}

想象一下如果需要以任意方式更改UI,然后比较使用此方法和QSignalMapper方法所需的代码更改。 - hyde
如果你的用户界面在运行时发生变化怎么办?不可能的,乔斯。 - Nolan Amy

4

由于有效参数是在执行时间而不是编译时间推导出来的,因此您无法将常量传递给connect()

但是,尽管这违反了OO原则,您可以使用QObject::sender() ,它会返回发射器QObject的指针。

以下是示例:

void YourClass::YourClass() :
  m_button1(new QPushButton()),
  m_button2(new QPushButton())
{
  connect(m_button1, SIGNAL(clicked()), this, SLOT(yourSlot()));
  connect(m_button2, SIGNAL(clicked()), this, SLOT(yourSlot()));
}

void YourClass::yourSlot()
{
  if ((QPushButton* button = dynamic_cast<QPushButton*>(sender()))
  {
    // Now button points to a QPushButton* that you can compare with the pointers you already have

    if (button == m_button1)
    {
      // Whatever
    } else
    if (button == m_button2)
    {
      // Whatever
    }
  }
}

如果您有很多按钮,您也可以使用QSignalMapper,为每个按钮提供一个标识符。

1
也可以使用objectName属性:http://doc.trolltech.com/4.5/qobject.html#objectName-prop - Magnus Hoff

3
现在,在连接时可以真正地绑定值。Qt5已经支持了这一功能。
例如:
connect(sender, &Sender::valueChanged,
    tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));

更多信息请参见此处

NB: 你当然也可以使用std::bind或boost::bind来代替tr1::bind。


0

如果你真的不想使用QSignalMapper,你可以尝试像这样做:

class SignalForwarderWithString: public QObject
{
    Q_OBJECT
public:
    SignalForwarderWithString(QString data = "", QObject *parent = 0) : QObject(parent), _data(data) {}
    QString _data;
signals:
    void forward(QString);
public slots:
    void receive() { emit forward(_data); }
};

...
connect(selecter1,SIGNAL(selected(QString)),this,SLOT(backgroundTypeChoiceMade(QString)));

SignalForwarderWithString *sfws;
sfws = new SignalForwarderWithString("button1", this);
connect(button1,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));

sfws = new SignalForwarderWithString("button2", this);
connect(button2,SIGNAL(clicked()), sfws, SLOT(receive(QString)));
connect(sfws, SIGNAL(forward(QString)), this,SLOT(backgroundTypeChoiceMade(QString)));

但是 QSignalMapper 同样容易使用...

QSignalMapper *mapper = new QSignalMapper(this);
connect(button1, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button1, "button 1");
connect(button2, SIGNAL(clicked()), mapper, SLOT(map()));
mapper->setMapping(button2, "button 2");
// you might have to tweak the argument type for your slot...
connect(mapper, SIGNAL(mapped(const QString &), this, SLOT(backgroundTypeChoiceMade(QString)));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接