0

uic 从.ui文件生成的输出始终将布局和子小部件安装在现有小部件上。当您打算直接在 a 上添加布局/子项时,这非常有用widget

#include "ui_form.h"

QWidget widget;
Ui::Form ui;
ui.setupUi(&widget);

唉,有时干预小部件是不必要的。首先,小部件上的布局具有非零边距,因此如果您打算将 的内容插入Ui::Form到布局中,则必须重置边距:

QWidget top;
QVBoxLayout layout{&top};
QWidget widget;
Ui::Form ui;
ui.setupUi(&widget);
widget.layout()->setContentsMargins(0,0,0,0);
layout.addWidget(&widget);

此外,在这种情况下,干预小部件是完全不需要的。假设我们setupUi不需要顶级小部件(从 Qt 5.7 开始,它在传递 nullptr 时崩溃)。我们可以按如下方式使用它:

QWidget top;
QVBoxLayout layout{&top};
Ui::Form ui;
ui.setupUi(nullptr);
layout.addLayout(ui.layout); // assuming `layout` is the top-level layout there

我已经检查过uic了,它不支持在没有顶级小部件的情况下发出代码。确实,所需的补丁是微不足道的。

然而,有没有办法在不修补 Qt的情况下实现nullptr-accepting的效果?setupUi

免得有人认为这是一个虚构的问题:这个问题部分是由 YUView 的这段代码提示的。在那里,parentWidget传递给setupUi已经有一个布局,目的是在完成后将表单的布局添加到另一个布局setupUiQWidget::setLayout从内部调用setupUi拒绝设置新布局,发出警告,尽管如此,事情还是发生了。代码很脆弱,因为它依赖于 Qt 代码以正确的方式破坏以产生所需的结果。

4

1 回答 1

1

是的。使我们能够这样做的关键观察结果是:

  1. AchildLayout可以通过将其父级设为空来从其父级布局中删除:

    childLayout->setParent(nullptr);
    
  2. 无父布局将在将其添加到小部件或具有 non-null 的布局时,重新设置parentWidget()其子级。

因此,我们需要向表单添加一个额外的顶级包装布局,然后从中取消目标顶级布局的父级,并删除该包装。报告的表单结构为dumpObjectTree

QWidget::Form 
    QVBoxLayout::wrapper 
        QVBoxLayout::layout 
            QHBoxLayout::horizontalLayout 
    QLabel::label 
    QLabel::label_2 
    QLabel::label_3

测试用例:

// https://github.com/KubaO/stackoverflown/tree/master/questions/layout-take-40497358
#include <QtWidgets>
#include "ui_form.h"

QLayout * takeLayout(QWidget *);

int main(int argc, char ** argv)
{
   QApplication app{argc, argv};

   QWidget parent;
   QHBoxLayout layout{&parent};
   Ui::Form ui;
   {
      QWidget w;
      ui.setupUi(&w);
      w.dumpObjectTree();
      if (true) {
         ui.layout->setParent(nullptr);
         delete ui.wrapper;
         layout.addLayout(ui.layout);
      } else {
         layout.addLayout(takeLayout(&w));
      }
   }
   parent.show();
   return app.exec();
}

如果您希望排除此功能,您将拥有一个模板setupLayout函数来代替setupUi. 上面的测试用例将变为:

   QWidget parent;
   QHBoxLayout layout{&parent};
   Ui::Form ui;
   layout.addLayout(setupLayout(&ui));
   parent.show();

setupLayout

//*** Interface

QLayout * setupLayout_impl(void * ui, void(*setupUi)(void * ui, QWidget * widget));

// Extracts the top layout from a Ui class generated by uic. The top layout must
// be enclosed in a wrapper layout.
template <typename Ui> QLayout * setupLayout(Ui * ui) {
  struct Helper {
    static void setupUi(void * ui, QWidget * widget) {
      reinterpret_cast<Ui*>(ui)->setupUi(widget);
    }
  };
  return setupLayout_impl(static_cast<void*>(ui), &Helper::setupUi);
}

//*** Implementation

static void unparentWidgets(QLayout * layout) {
  const int n = layout->count();
  for (int i = 0; i < n; ++i) {
    QLayoutItem * item = layout->itemAt(i);
    if (item->widget()) item->widget()->setParent(0);
    else if (item->layout()) unparentWidgets(item->layout());
  }
}

QLayout * setupLayout_impl(void * ui, void(*setupUi)(void * ui, QWidget * widget))
{
  QWidget widget;
  setupUi(ui, &widget);
  QLayout * wrapperLayout = widget.layout();
  Q_ASSERT(wrapperLayout);
  QObjectList const wrapperChildren = wrapperLayout->children();
  Q_ASSERT(wrapperChildren.size() == 1);
  QLayout * topLayout = qobject_cast<QLayout*>(wrapperChildren.first());
  Q_ASSERT(topLayout);
  topLayout->setParent(0);
  delete wrapperLayout;
  unparentWidgets(topLayout);
  Q_ASSERT(widget.findChildren<QObject*>().isEmpty());
  return topLayout;
}

如果您不能或不愿意更改表单的结构怎么办?您可以使用 Qt 的内部实现一个takeLayout函数,该函数将一个小部件的布局剥离并返回,不附加到小部件上。请注意,私有QWidget::takeLayout是不够的,因为它返回一个带有其topLevel标志集的布局:此类布局只能直接安装在小部件上,并且当尝试将它们添加到布局(作为子布局)时将失败断言。就是这样:

#include <private/qwidget_p.h>
#include <private/qlayout_p.h>

class WidgetHelper : private QWidget {
   struct LayoutHelper : private QLayout {
      static void resetTopLevel(QLayout * l) {
         auto d = static_cast<QLayoutPrivate*>(static_cast<LayoutHelper*>(l)->d_ptr.data());
         d->topLevel = false;
      }
   };
public:
   static QLayout * takeLayout(QWidget * w) {
      auto d = static_cast<QWidgetPrivate*>(static_cast<WidgetHelper*>(w)->d_ptr.data());
      auto l = w->layout();
      if (!l) return nullptr;
      d->layout = 0;
      l->setParent(nullptr);
      LayoutHelper::resetTopLevel(l);
      return l;
   }
};
QLayout * takeLayout(QWidget * w) { return WidgetHelper::takeLayout(w); }

form.ui外观如下:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Form</class>
 <widget class="QWidget" name="Form">
  <layout class="QVBoxLayout" name="wrapper">
   <item>
    <layout class="QVBoxLayout" name="layout">
     <item>
      <widget class="QLabel" name="label">
       <property name="text">
        <string>Top</string>
       </property>
      </widget>
     </item>
     <item>
      <layout class="QHBoxLayout" name="horizontalLayout">
       <item>
        <widget class="QLabel" name="label_2">
         <property name="text">
          <string>Left</string>
         </property>
        </widget>
       </item>
       <item>
        <widget class="QLabel" name="label_3">
         <property name="text">
          <string>Right</string>
         </property>
        </widget>
       </item>
      </layout>
     </item>
    </layout>
   </item>
  </layout>
 </widget>
</ui>
于 2016-11-08T22:14:35.450 回答