【问题标题】:Binding arguments to signals/slots将参数绑定到信号/插槽
【发布时间】:2010-07-02 13:27:45
【问题描述】:

我基本上有多个要连接到同一个插槽的事件信号。我想知道的是如何将基于字符串的参数传递给同一个插槽,以便插槽知道这个信号来自哪个。一种替代方法是制作与信号一样多的插槽,然后以 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")));

错误与我在最后 2 个命令中传递的参数有关。backgroundTypeChoiceMade 声明如下:

void backgroundTypeChoiceMade(QString);

谁能告诉我上面代码中的错误是什么?

【问题讨论】:

    标签: c++ qt


    【解决方案1】:

    您可以使用QSignalMapper。尽管 QSignalMapper 是您问题的答案,但我认为 jon hanson 的答案是您应该采取的方式。这样你会得到更简洁的代码。

    【讨论】:

    • 我认为在这种情况下 QSignalMapper 是要走的路。想象一下将按钮更改为 QComboBox 或任何其他 UI 更改。使用 QSignalMapper,在 .cpp 文件中进行微不足道的更改。使用单独的插槽,需要更改类签名。
    【解决方案2】:

    四种方法。一个不烂。

    1. QSignalMapper。有效,但会导致代码混乱。
    2. 已命名的插槽。对于大量的发件人来说都是混乱的,并且不适用于动态生成的发件人(例如,列表中的按钮)。
    3. sender()-比较。可以处理动态发件人,但还是有点丑。
    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()、子类化和信号映射器。

    警告:显然最适合少数发件人类型。但通常情况就是这样。在这种情况下,这是值得的。

    【讨论】:

    • 我认为每次(合理地)可以避免使用 QSignalMapper。最好的解决方案(导致更清晰的代码)是:1)子类化并在@Foamy 所写的信号中发送您想要的ID。我们尽可能在 Qt 项目中使用它(但子类化并不是一个好主意)并且 2)在您的插槽中获取 sender(),对其进行类型检查并与您想要的任何 ID/标签和发件人进行比较有。
    • @ViktorBenei 我同意,但我认为只为参数化信号进行子类化是值得的。在子类化方面需要做更多的工作,但可以让您的其余代码更加简洁。
    【解决方案3】:

    使用单独的插槽有什么效率低下的地方?如果插槽处理程序中存在共性,则将其移至函数中,例如扩展 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 方法进行比较。
    • 如果您的 UI 在运行时发生变化怎么办?不可能,何塞。
    【解决方案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

    【讨论】:

    【解决方案5】:

    您现在可以真正在连接时绑定一个值。 Qt5 增加了对此的支持。

    例子:

    connect(sender, &Sender::valueChanged,
        tr1::bind(receiver, &Receiver::updateValue, "senderValue", tr1::placeholder::_1));
    

    more info

    注意:您当然可以使用 std::bind 或 boost::bind 代替 tr1::bind。

    【讨论】:

      【解决方案6】:

      如果你真的不想使用 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)));
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-30
        • 1970-01-01
        • 2016-10-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多