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

IT技术 reactjs qt design-patterns pyside2
2021-05-08 17:51:40

在 Qt/PySide2 中,是否有 Qt 小部件之类的东西,它可以简单地传递到包装的小部件,而无需添加任何额外的布局层等。

我来自 Web 前端背景,所以我的心智模型是一个 React容器组件,它添加了一些行为,然后简单地呈现一个包装的展示组件。

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

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

3个回答

首先我要问,创建一个容器小部件来只保存一个小部件,没有额外的填充、布局或其他“开销”有什么意义?为什么不只显示将包含的小部件?

其次,没有什么说你必须有一个QLayout内部 a QWidget布局简单地QWidget::setGeometry()在子小部件上使用(或类似方式)移动任何包含的小部件实现一个QWidget使子小部件的大小与它自己的大小相匹配的方法是微不足道的,尽管它相当毫无意义,因为这就是QLayout它的目的。但我在下面包含了这样一个例子(C++,抱歉)

上的顶级QLayoutQWidget具有默认的内容边距(围绕所包含的小部件填充)。这可以很容易地删除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 文档中转换示例非常相似(并开始使用)

什么下面实际上是部分代码做并不重要,关键是如何他们正在实施,再次明确意味着不同的方法说明一个“控制器”的视觉元素。

第一个例子是东西一个小部件,即,从继承QWidget(或QFrame在这种情况下),并使用其他部件来呈现“统一” UI和API。这是两个double的编辑器,例如大小宽度/高度或坐标 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;
};

在此处输入图片说明

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

文档说:

...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用于包含小部件,则无需创建布局。

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

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

但是,我不确定它是否 100% 是您正在寻找的,因为我不熟悉您概述的 React 方法。也不确定在不使用布局的情况下可以合理地走多远。