Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing signals through hierarchies

Tags:

qt

I am having some difficulty fully grasping how signals and slots are used in Qt. I am sure it is really basic but I'm just no getting it today.

I have a set of widgets a bit like this:

MainWindow -->StackedWidget -->ChildForms

Now the idea is that there are some actions on the Child widgets that will cause the stacked widget to display a different page.

So if I understand it properly I thought the way to connect signals and slots is to use the connect() at the scope that knows about the objects but what I have managed to get working doesn't do it this way. At the moment in my child form I use parentWidget() to access the slot of the StackedWidget but I am not very happy with really because it is giving the child information about the parent which it shouldn't have:

void TaskSelectionForm::setButtonMappings()
{
    // Set up a mapping between the buttons and the pages
    QSignalMapper *mapper = new QSignalMapper(this);
    connect(mapper, SIGNAL(mapped(int)), parentWidget(), SLOT(setCurrentIndex(int)));

    mapper->setMapping(ui->utilitiesButton, 2); // Value of the index
    connect(ui->utilitiesButton, SIGNAL(clicked()), mapper, SLOT(map()));
}

But I am not really sure how I should do this and connect it up. Do I need to have signals at each level and emit through the tree?

like image 637
Firedragon Avatar asked May 28 '12 11:05

Firedragon


1 Answers

A Bit of Signal-Slot Theory

The signal-slot connections are oblivious to parent-child relationships between QObjects, and any such relationship doesn't matter. You're free to connect objects to their children, to their siblings, to their parents, or even to QObjects that are in a separate hierarchy, or to lone QObjects that have neither parents nor children. It doesn't matter.

A signal-slot connection connects a signal on a particular instance of QObject to slot on another instance of QObject. To use the connect method, you need the pointers to the instance of sender QObject and the instance of receiver QObject. You then use the static QObject::connect(sender, SIGNAL(...), receiver, SLOT(...)). Those connections have nothing to do with any hierarchy there is between the sender and receiver.

You can also connect a signal to a signal, to forward it -- for example from a private UI element to a signal that's part of the API of the class. You cannot connect a slot to a slot, because it'd incur a bit of runtime overhead for a rarely-used case. The overhead would be an extra bool member in QObjectPrivate, plus a failed if (bool) test. If you want to forward slots to slots, there are at least two ways to do it:

  1. Emit a signal in the source slot and connect that signal to the destination slot.

  2. Obtain a list of all signals connected to the source slot, iterate on it and connect them to to the target slot. There's no easy way to maintain such connections when further signals are connected or disconnected from the source slot. Unfortunately, QObject only has a connectNotify(const char*) protected function, but not a signal -- so you can't hook up to it unless you would modify src/corelib/kernel/qobject[.cpp,_p.h,.h] to emit such a signal. If you truly need it, just modify the Qt source, you have access it for a reason, after all. Hacking the vtable without modifying Qt is possible, but discouraged for obvious reasons.

The Answer

Below is a self contained example that shows how to do what you want. Turns out I have answers to quite a few questions from my various experiments I've done in Qt in the past. I'm a packrat when it comes to test code. It's all SSCCE to boot :)

// https://github.com/KubaO/stackoverflown/tree/master/questions/signal-slot-hierarchy-10783656
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif

class Window : public QWidget
{
   QSignalMapper m_mapper;
   QStackedLayout m_stack{this};
   QWidget      m_page1,                  m_page2;
   QHBoxLayout  m_layout1{&m_page1},      m_layout2{&m_page2};
   QLabel       m_label1{"Page 1"},       m_label2{"Page 2"};
   QPushButton  m_button1{"Show Page 2"}, m_button2{"Show Page 1"};
public:
   Window(QWidget * parent = {}) : QWidget(parent) {
      // the mapper tells the stack which page to switch to
      connect(&m_mapper, SIGNAL(mapped(int)), &m_stack, SLOT(setCurrentIndex(int)));

      // Page 1
      m_layout1.addWidget(&m_label1);
      m_layout1.addWidget(&m_button1);
      // tell the mapper to map signals coming from this button to integer 1 (index of page 2)
      m_mapper.setMapping(&m_button1, 1);
      // when the button is clicked, the mapper will do its mapping and emit the mapped() signal
      connect(&m_button1, SIGNAL(clicked()), &m_mapper, SLOT(map()));
      m_stack.addWidget(&m_page1);

      // Page 2
      m_layout2.addWidget(&m_label2);
      m_layout2.addWidget(&m_button2);
      // tell the mapper to map signals coming from this button to integer 0 (index of page 1)
      m_mapper.setMapping(&m_button2, 0);
      connect(&m_button2, SIGNAL(clicked()), &m_mapper, SLOT(map()));
      m_stack.addWidget(&m_page2);
   }
};

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Window w;
   w.show();
   return a.exec();
}
like image 93
Kuba hasn't forgotten Monica Avatar answered Sep 27 '22 20:09

Kuba hasn't forgotten Monica