signals and slots in multilayered UI widgets
Asked Answered
I

3

4

Let's say we have the follogin UI:

+--------------------------+
|W1       +--------------+ |
|         |W2            | |
|         | +----------+ | |
|         | |W3        | | |
|         | +----------+ | |
|         |              | |
|         +--------------+ |
+--------------------------+

W3 is interested in a certain signal emited in W1 (or a level below, i.e. qApp).

The idea is to develop W3 independently. But somebody has to do the signal/slot connection.

What would be a good practice/recommended way of connecting the signal emited in W1 into slot in W3 if we want W3 not to know about any other widget, and we don't want W1 to know about W3 either?

Solution 1: Connect signal in W1 with signal in W2 (signal to signal technique) and therefore connect signal in W2 to slot in W3.

Solution 2: Generate a qApp signal and connect it in W2 with slot in W3.

Solution 3: Generate a qApp signal and have W3 itself connect to it using its own slot.

Thanks

Interruption answered 3/8, 2010 at 17:10 Comment(0)
P
4

Usually the enclosing widget encapsulates the widgets inside it and provides a higher-level interface. E.g., if W3 contains several widgets that need to be enabled/disabled depending on some state, W3 would manage that state, provide API for it and enable/disable widgets accordingly. So usually there is no need to go directly from W1 to W3.

If the widgets don't know about each other (e.g. b/c W1 is a generic container widget that can embed anything), then the one who assembles the UI, knowing all involved widgets, should do the connection.

I don't know what you mean by "qApp signal", but that sounds too much like on central object being connected to everything, which of course isn't good design either.

Maybe you have a specific example you had in mind?

Purifoy answered 3/8, 2010 at 20:46 Comment(2)
Thanks for your answer. I think it makes sense to just encapsulate what W3 needs in W2. W2 just needs to expose a slot, and whoever is connecting them would be in charge of that slot to be called. After that, it would be the job of W2 to use this information for W3. *** qApp is a macro that comes with Qt to have access to the QApplication object, and is accessible from anywhere in the code that has its roots in it. Signals emited in the QApplication instance can be connected by doing: connect(qApp, SIGNAL(someCustomSignal() ), this, SLOT(someSlot()));Interruption
Yeah, then I understood your qApp idea correctly. (and my critical argument against it applies ;)). Personally I avoid using QApplication in the widget as far as possible. Assuming a certain QApplication subclass with custom signals/slots ties it even more together. Usually I don't subclass QApplication at all. I rather let some widgets know about each other than tying them together implicitely and well hidden via an allmighty QApplication object.Purifoy
S
2

The approach we use here is that "the shell" makes the connections.

The shell is an object that knows about all the widgets involved. In this case, W1, W2, and W3. Usually it is the code that assembles the user interface.

If the shell doesn't know about W3 (because W3 is an implementation detail, for example) then W3's "owner" should make the connection and so on.

Sakai answered 3/8, 2010 at 18:50 Comment(2)
Thanks for the response. The issue I find with your approach is that, if W3 contains another widget W4, which in turn contains W5, and so on, and the last widget in the chain needs to know about the signal emited from W1, it would mean to make a connection at every level. Is this acceptable in common practices?Interruption
Are W4, W5, etc... part of the public interface of W3? If they are, and the connections are not required for the whole composite to work, then do it outside of W3. Else, encapsulation will be violated.Sakai
E
0

You might set name to widgets and then discover them anywhere:

for(auto w_ptr: qApp->allWidgets())
    if(w_ptr->objectName() == "QObject anywhere")
        connect(...)

or in the parent widget:

if(QPushButton* o = findChild<QPushButton*>("QPushButton with name"))
    connect(...)

main.cpp

#include "widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QWidget wdgt;
    wdgt.setWindowTitle("wdgt");
    wdgt.setObjectName("wdgt");
    wdgt.show();

    Widget w;
    w.show();

    return a.exec();
}

widget.cpp

#include <qlayout>
#include <qpushButton>
#include <qdebug>
#include <qapplication>

#include "widget.h"
#include "connect_by_name.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    setWindowTitle("Widget");

    m_label->setFrameShape(QFrame::Box);

    QHBoxLayout * hl{new QHBoxLayout{}};

    hl->addWidget(m_label);
    hl->addWidget(new connect_by_name{});
    setLayout(hl);

    connect_to_unique_pb();
}

void Widget
::connect_to_unique_pb() {
    if(QPushButton * pb_ptr
            = findChild<QPushButton*>("unique_pb"))
    {
        connect(pb_ptr,  &QPushButton::pressed,
                m_label, &QLabel::clear);
        connect(pb_ptr,  &QPushButton::released,
                this,    &Widget::pb_relased);
    }
    else
    {
        qDebug() << "The push button not found.";
    }
}

void Widget
::pb_relased() {
    m_label->setText("button not pressed");
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <qwidget>
#include <QLabel>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);

public slots:
    void pb_relased();

private:
    void connect_to_unique_pb();

private:
    QLabel * m_label{new QLabel{"button not pressed"}};
};

#endif // WIDGET_H

connect_by_name.cpp

#include <qapplication>
#include <qdebug>
#include <qwidget>

#include "connect_by_name.h"
#include "widget.h"

connect_by_name
::connect_by_name(QWidget *parent) :
    QWidget(parent)
{
    m_pb->setObjectName("unique_pb");
    m_hl->addWidget(m_pb);
    connect_to_unique_widget();
}

void connect_by_name
::connect_to_unique_widget() {
    for(auto w_ptr: qApp->allWidgets())
        if(w_ptr->objectName() == "wdgt") {
            connect(m_pb,  &QPushButton::pressed,
                    w_ptr, &QWidget::hide);
            connect(m_pb,  &QPushButton::released,
                    w_ptr, &QWidget::show);
            break;
        }
}

connect_by_name.h

#ifndef CONNECT_BY_NAME_H
#define CONNECT_BY_NAME_H

#include <QWidget>
#include <QPushButton>
#include <QLayout>

class connect_by_name : public QWidget
{
    Q_OBJECT

public:
    explicit connect_by_name(QWidget *parent = nullptr);

private:
    void connect_to_unique_widget();

private:
    QHBoxLayout * m_hl {new QHBoxLayout{this}};
    QPushButton * m_pb {new QPushButton{"unique button"}};
};

#endif // CONNECT_BY_NAME_H

connect.pro

QT       += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET   = connect
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS

SOURCES += \
        main.cpp \
        widget.cpp \
        connect_by_name.cpp

HEADERS += \
        widget.h \
        connect_by_name.h
Eggshaped answered 7/2, 2019 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.