Qt“直通”或“容器”小部件

时间:2019-10-23 10:18:50

标签: reactjs qt design-patterns pyside2

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

我来自Web前端背景,所以我的思维模型是React container component的模型,它添加了一些行为,但随后仅呈现了一个包装好的表示组件。

但是,在Qt中似乎没有一种方法,至少在包装小部件中不创建布局,即使该布局仅包含一个小部件也是如此。我可以看到,这可能会导致多层冗余布局,这可能会导致效率低下。

我承认最好不要尝试在Qt中复制React模式,因此也欢迎任何等效但更惯用的模式的建议。

3 个答案:

答案 0 :(得分:1)

在Qt中有两种管理窗口小部件的方法:通过布局或通过父项。您是否尝试过使用'parent'方法?

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.

因此,基本上,如果您使用setParent来包含小部件,则无需创建布局。

答案 1 :(得分:1)

首先,我要问的是,创建一个容器小部件以仅容纳一个小部件而没有多余的填充,布局或其他“开销”的意义是什么?为什么不只显示将包含的小部件?

第二,没有任何内容表明您必须在QLayout中包含QWidget。布局仅使用子窗口小部件上的QWidget::setGeometry()(或类似名称)来移动所有包含的窗口小部件。实施QWidget来调整子窗口小部件的大小以匹配其自身大小是很简单的,尽管它是毫无意义的,因为这就是QLayout的目的。但是我在下面包括了这样一个例子(对不起,C ++)

QLayout上设置的顶级QWidget具有默认的内容边距(在包含的窗口小部件周围填充)。可以使用QLayout::setContentMargins(0, 0, 0, 0)轻松地将其删除(如先前的评论中所述)。

“无布局”“直通” 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.
}

添加:扩展我关于成为小部件与拥有(或管理)小部件的评论。

我碰巧正在开发一个实用程序应用程序,它在两个部分中同时使用了两种范例。我不会包含所有代码,但希望有足够的理解。请参阅下面的屏幕快照,了解如何使用它们。 (该应用程序用于测试我正在做的绘画和转换代码,与Qt文档中的Transformations Example类似(并且开始使用)。)

以下代码部分的实际作用并不重要,关键是它们的实现方式,同样专门用于说明“控制器”的不同方法。 “用于视觉元素。

第一个示例是成为小部件,即从QWidget(在本例中为QFrame)继承并使用其他小部件来呈现“统一” UI和API。这是两个double值的编辑器,例如大小的width / height或x / y坐标值。可以链接这两个值,因此更改其中一个也会更改另一个以匹配。

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

对于另一个示例,现在使用一个管理一组小部件但本身不显示任何内容的“控制器” QObject。这些小部件可供管理应用程序按需放置,而控制器提供了用于与小部件和数据进行交互的统一API。可以根据需要创建或销毁控制器。

此示例管理一个QWidget,它是用于进行一些自定义绘制的“渲染区域”,以及一个“设置” QWidget,用于更改渲染区域中的属性。设置窗口小部件还具有其他子窗口小部件,但是这些子窗口小部件没有直接显示给控制应用程序。实际上,它还利用了上方的ValuePairEditor

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

enter image description here

答案 2 :(得分:1)

我以类似的方式使用了QFrame,所有属性都设置为0,Shape设置为QFrame::NoFrame。作为最终完成繁重工作的实际小部件的哑容器,它非常有用,QStackedWidget是其中的一个用户。引用文档:

  

QFrame类也可以直接用于创建没有任何内容的简单占位符框架。

但是,由于我不熟悉您概述的React方法,因此我不确定它是否是您所寻找的100%。同样不确定在不使用布局的情况下可以合理走多远。