Qt“直通”或“容器”小部件 [英] Qt "passthrough" or "container" widget

查看:91
本文介绍了Qt“直通”或“容器”小部件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Qt / PySide2中,有一个Qt小部件可以直接传递到包装的小部件,而无需添加任何其他布局层。



我来自Web前端背景,因此我的思维模型是React


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.

解决方案

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

这篇关于Qt“直通”或“容器”小部件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆