问题
In Qt / PySide2, is there such a thing as a Qt widget that simply passes through to a wrapped widget, without adding any extra layers of layout etc.
I'm coming from a web frontend background, so my mental model is of a React container component that adds some behavior but then simply renders a wrapped presentational component.
However, there doesn't seem to be a way of doing this sort of thing in Qt without at least creating a layout in the wrapping widget, even if that layout only contains one widget. I could see that this could lead to multiple layers of redundant layout, which could be inefficient.
I acknowledge that it may be better to not try to replicate React patterns in Qt, so any suggestions of equivalent but more idiomatic patterns would also be welcome.
回答1:
First I have to ask, what is the point of creating a container widget to just hold one widget, with no extra padding, layouts, or other "overhead?" Why not just show the widget which would be contained?
Second, nothing says you must have a QLayout
inside a QWidget
. The layout simply moves any contained widgets around using QWidget::setGeometry()
(or similar) on the child widget(s). It's trivial to implement a QWidget
which sizes a child widget to match its own size, though it's fairly pointless because that's what QLayout
is for. But I have included such an example below (C++, sorry)
A top-level QLayout
set on a QWidget
has default content margins (padding around the contained widget(s)). This can easily be removed with QLayout::setContentMargins(0, 0, 0, 0) (as mentioned in a previous comment).
"No-layout" "passthrough" QWidget
:
#include <QWidget>
class PassthroughWidget : public QWidget
{
Q_OBJECT
public:
PassthroughWidget(QWidget *child, QWidget *parent = nullptr) :
QWidget(parent),
m_child(child)
{
if (m_child)
m_child->setParent(this); // assume ownership
}
protected:
void resizeEvent(QResizeEvent *e) override
{
QWidget::resizeEvent(e);
if (m_child)
m_child->setGeometry(contentsRect()); // match child widget to content area
}
QWidget *m_child; // Actually I'd make it a QPointer<QWidget> but that's another matter.
}
ADDED: To expand on my comments regarding being a widget vs. having (or managing) widget(s).
I just happen to be working on a utility app which makes use of both paradigms for a couple of parts. I'm not going to include all the code, but hopefully enough to get the point across. See screenshot below for how they're used. (The app is for testing some painting and transform code I'm doing, quite similar to (and started life as) the Transformations Example in Qt docs.)
What the code parts below actually do isn't important, the point is how they're implemented, again specifically meant to illustrate the different approaches to a "controller" for visual elements.
First example is of something being a widget, that is, inheriting from QWidget
(or QFrame
in this case) and using other widgets to present a "unified" UI and API. This is an editor for two double
values, like for a size width/height or coordinate x/y value. The two values can be linked so changing one will also change the other to match.
class ValuePairEditor : public QFrame
{
Q_OBJECT
public:
typedef QPair<qreal, qreal> ValuePair;
explicit ValuePairEditor(QWidget *p = nullptr) :
QFrame(p)
{
setFrameStyle(QFrame::NoFrame | QFrame::Plain);
QHBoxLayout *lo = new QHBoxLayout(this);
lo->setContentsMargins(0,0,0,0);
lo->setSpacing(2);
valueSb[0] = new QDoubleSpinBox(this);
...
connect(valueSb[0], QOverload<double>::of(&QDoubleSpinBox::valueChanged),
this, &ValuePairEditor::onValueChanged);
// ... also set up the 2nd spin box for valueSb[1]
linkBtn = new QToolButton(this);
linkBtn->setCheckable(true);
....
lo->addWidget(valueSb[0], 1);
lo->addWidget(linkBtn);
lo->addWidget(valueSb[1], 1);
}
inline ValuePair value() const
{ return { valueSb[0]->value(), valueSb[1]->value() }; }
public slots:
inline void setValue(qreal value1, qreal value2) const
{
for (int i=0; i < 2; ++i) {
QSignalBlocker blocker(valueSb[i]);
valueSb[i]->setValue(!i ? value1 : value2);
}
emit valueChanged(valueSb[0]->value(), valueSb[1]->value());
}
inline void setValue(const ValuePair &value) const
{ setValue(value.first, value.second); }
signals:
void valueChanged(qreal value1, qreal value2) const;
private slots:
void onValueChanged(double val) const {
...
emit valueChanged(valueSb[0]->value(), valueSb[1]->value());
}
private:
QDoubleSpinBox *valueSb[2];
QToolButton *linkBtn;
};
Now for the other example, using a "controller" QObject
which manages a set of widgets, but doesn't itself display anything. The widgets are available to the managing application to place as needed, while the controller provides a unified API for interacting with the widgets & data. Controllers can be created or destroyed as needed.
This example manages a QWidget
which is a "render area" for doing some custom painting, and a "settings" QWidget
which changes properties in the render area. The settings widget has further sub-widgets, but these are not directly exposed to the controlling application. In fact it also makes use of ValuePairEditor
from above.
class RenderSet : public QObject
{
Q_OBJECT
public:
RenderSet(QObject *p = nullptr) :
QObject(p),
area(new RenderArea()),
options(new QWidget())
{
// "private" widgets
typeCb = new QComboBox(options);
txParamEdit = new ValuePairEditor(options);
...
QHBoxLayout *ctrLo = new QHBoxLayout(options);
ctrLo->setContentsMargins(0,0,0,0);
ctrLo->addWidget(typeCb, 2);
ctrLo->addWidget(txParamEdit, 1);
ctrLo->addLayout(btnLo);
connect(txParamEdit, SIGNAL(valueChanged(qreal,qreal)), this, SIGNAL(txChanged()));
}
~RenderSet() override
{
if (options)
options->deleteLater();
if (area)
area->deleteLater();
}
inline RenderArea *renderArea() const { return area.data(); }
inline QWidget *optionsWidget() const { return options.data(); }
inline Operation txOperation() const
{ return Operation({txType(), txParams()}); }
inline TxType txType() const
{ return (typeCb ? TxType(typeCb->currentData().toInt()) : NoTransform); }
inline QPointF txParams() const
{ return txParamEdit ? txParamEdit->valueAsPoint() : QPointF(); }
public slots:
void updateRender(const QSize &bounds, const QPainterPath &path) const {
if (area)
...
}
void updateOperations(QList<Operation> &operations) const {
operations.append(txOperation());
if (area)
...
}
signals:
void txChanged() const;
private:
QPointer<RenderArea> area;
QPointer<QWidget> options;
QPointer<QComboBox> typeCb;
QPointer<ValuePairEditor> txParamEdit;
};
回答2:
There are two ways of managing widgets in Qt: by layouts or by parentness. Have you tried to use the 'parent'
approach?
Docs says:
...The base class of everything that appears on the screen, extends the parent-child relationship. A child normally also becomes a child widget, i.e. it is displayed in its parent's coordinate system and is graphically clipped by its parent's boundaries.
So, basically, if you use setParent
for containing widgets, no layouts need to be created.
回答3:
I have used QFrame in a similar manner, with all properties set to 0 and Shape
set to QFrame::NoFrame
. It is very useful as a dumb container for the actual widgets that end up doing the heavy lifting, QStackedWidget
is one user of this. Quote from the docs:
The QFrame class can also be used directly for creating simple placeholder frames without any contents.
However, I'm not sure it is 100% what you are looking for, as I'm not familiar with the React method you are outlining. Also not sure how far you can reasonably get without using layouts.
来源:https://stackoverflow.com/questions/58520625/qt-passthrough-or-container-widget