Передача сигналов через иерархии

У меня возникают трудности с пониманием того, как сигналы и слоты используются в Qt. Я уверен, что это действительно простой, но я просто не понимаю его сегодня.

У меня есть набор виджетов примерно так:

MainWindow → StackedWidget → ChildForms

Теперь идея состоит в том, что в дочерних виджетах есть некоторые действия, которые заставят многоуровневый виджетов отображать другую страницу.

Поэтому, если я правильно понимаю, я думал, что способ подключения сигналов и слотов - использовать connect() в области, которая знает об объектах, но то, что мне удалось получить, не делает этого таким образом. На данный момент в моей дочерней форме я использую parentWidget() для доступа к слоту StackedWidget, но я не очень доволен тем, что он предоставляет дочернюю информацию о родителе, которого он не должен иметь:

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()));
}

Но я не совсем уверен, как это сделать и подключить его. Нужно ли мне иметь сигналы на каждом уровне и излучать через дерево?

Ответ 1

Немного теории сигнальных слотов

Соединения сигнальных слотов не обращают внимания на отношения родитель-потомок между QObjects, и любые такие отношения не имеют значения. Вы можете подключать объекты к своим детям, их братьям и сестрам, их родителям или даже к QObjects, которые находятся в отдельной иерархии, или к одиноким QObjects, у которых нет ни родителей, ни детей. Это не имеет значения.

Соединение сигнального слота соединяет сигнал на конкретном экземпляре объекта QObject в слот в другом экземпляре объекта QObject. Чтобы использовать метод connect, вам нужны указатели на экземпляр QObject отправителя и экземпляр QObject получателя. Затем вы используете статический QObject::connect(sender, SIGNAL(...), receiver, SLOT(...)). Эти соединения не имеют ничего общего с какой-либо иерархией между отправителем и получателем.

Вы также можете подключить сигнал к сигналу, чтобы переслать его - например, из частного элемента пользовательского интерфейса в сигнал той части API класса. Вы не можете подключить слот к слоту, потому что он может повлечь за собой чрезмерные издержки во время выполнения для редко используемого случая. Накладные расходы будут дополнительным членом bool в QObjectPrivate, плюс неудачный тест if (bool). Если вы хотите пересылать слоты в слоты, есть как минимум два способа сделать это:

  1. Выпустите сигнал в исходном слоте и подключите этот сигнал к разъему назначения.

  2. Получите список всех сигналов, подключенных к исходному слоту, идите на него и подключите их к целевому слоту. Там нет простого способа поддерживать такие соединения, когда дополнительные сигналы подключены или отсоединены от исходного разъема. К сожалению, QObject имеет только функцию connectNotify(const char*), но не сигнал, поэтому вы не можете подключиться к ней, если вы не измените src/corelib/kernel/qobject[.cpp,_p.h,.h] излучать такой сигнал. Если вам это действительно нужно, просто измените источник Qt, вы получите доступ к нему по какой-то причине. Взлом vtable без изменения Qt возможен, но он обескуражен по понятным причинам.

Ответ

Ниже приведен пример, который показывает, как делать то, что вы хотите. Оказывается, у меня есть ответы на несколько вопросов из моих различных экспериментов, которые я делал в Qt в прошлом. Когда речь заходит о тестовом коде, я - пакет. Все это SSCCE для загрузки :)

// 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();
}

Ответ 2

Connect(stackedwidget->currentactivewidget,SIGNAL(OnAction()),this,SLOT(PrivateSlot()));

PrivateSlot() - это слот, объявленный конфиденциально. Таким образом, в этой функции вы можете добавить свой код, чтобы изменить страницу stackedwidget, соответствующую действию, создаваемому currentactivewidget.

Опять же, если вы действительно хотите передать сигнал вверх по иерархии, испустите publicsignal() в конце функции частного слота.

Connect(this,SIGNAL(publicsignal()),Parentwidgetofstackedwidget(here mainwindow),SLOT(mainwindow_slot()));