Qt виджет "passthrough" или "контейнер" - PullRequest
1 голос
/ 23 октября 2019

В Qt / PySide2 есть такая вещь, как виджет Qt, который просто проходит через обернутый виджет, без добавления каких-либо дополнительных слоев макета и т. Д.

Я исходил из фона веб-интерфейсаТак что моя ментальная модель имеет контейнерный компонент React , который добавляет некоторое поведение, но затем просто отображает упакованный компонент представления.

Однако, похоже, нет способа сделать эточто-то вроде этого в Qt, по крайней мере, не создавая макет в виджете переноса, даже если этот макет содержит только один виджет. Я мог видеть, что это может привести к нескольким слоям избыточной компоновки, что может быть неэффективно.

Я признаю, что может быть лучше не пытаться реплицировать шаблоны React в Qt, поэтому любые предложения эквивалентных, но более идиоматическихшаблоны также будут приветствоваться.

Ответы [ 3 ]

1 голос
/ 01 ноября 2019

Сначала я должен спросить, какой смысл создавать контейнерный виджет, чтобы он просто содержал один виджет, без дополнительных отступов, макетов или других «накладных расходов»? Почему бы просто не показать виджет, который будет содержаться?

Во-вторых, ничто не говорит о том, что у вас должно быть QLayout внутри QWidget. Макет просто перемещает любые содержащиеся в нем виджеты, используя QWidget::setGeometry() (или аналогичный) для дочерних виджетов. Тривиально реализовать QWidget, который устанавливает размер дочернего виджета в соответствии с его собственным размером, хотя это довольно бессмысленно, потому что именно для этого QLayout. Но я включил такой пример ниже (C ++, извините)

У верхнего уровня QLayout, установленного на QWidget, есть поля содержимого по умолчанию (с отступом вокруг содержащихся виджетов). Это может быть легко удалено с помощью QLayout::setContentMargins(0, 0, 0, 0) (как упомянуто в предыдущем комментарии).

"Без макета" "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.
}

ДОБАВЛЕНО : Чтобы расширить мои комментарии относительно , являющегося виджетом, против , имеющего (или управляющего) виджета (ов).

Просто я работаю над служебным приложением, которое использует обе парадигмы для нескольких частей. Я не собираюсь включать весь код, но, надеюсь, достаточно, чтобы понять суть. Смотрите скриншот ниже, чтобы узнать, как они используются. (Приложение для тестирования некоторого кода рисования и преобразования, которое я делаю, очень похоже на (и начал жизнь как) Пример преобразований в документах Qt.)

Что части кода, приведенные ниже, на самом деле не важны, дело в том, как они реализованы, опять-таки специально для иллюстрации различных подходов к "контроллеру" для визуальных элементов.

Первый пример - это то, что является виджетом, то есть наследуется от QWidget (или QFrame в данном случае) и использует другие виджеты для представления "унифицированного" пользовательского интерфейса и 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, который является «областью рендеринга» для выполнения некоторой пользовательской рисования, и «settings» 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

1 голос
/ 01 ноября 2019

Я использовал QFrame аналогичным образом, со всеми свойствами, установленными на 0, а Shape на QFrame::NoFrame. Это очень полезно как тупой контейнер для реальных виджетов, которые в конечном итоге выполняют тяжелую работу, QStackedWidget является одним из пользователей этого. Цитата из документов:

Класс QFrame также можно использовать непосредственно для создания простых рамок-заполнителей без содержимого.

Однако я не уверен, что это 100%, что вы ищете, так как я не знаком с методом React, который вы описали. Также не уверен, насколько далеко вы можете разумно пройти без использования макетов.

1 голос
/ 28 октября 2019

Существует два способа управления виджетами в 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 для содержания виджетов, не нужно создавать макеты,

...