如何使用 setupUi 或 uic 输出来创建没有父小部件的独立布局?
Posted
技术标签:
【中文标题】如何使用 setupUi 或 uic 输出来创建没有父小部件的独立布局?【英文标题】:How to use setupUi or uic output to create a free-standing layout without a parent widget? 【发布时间】:2016-11-08 22:14:35 【问题描述】:uic 从.ui
文件生成的输出总是将布局和子小部件安装在现有小部件上。当您打算直接在 widget
上添加布局/子项时,这非常有用:
#include "ui_form.h"
QWidget widget;
Ui::Form ui;
ui.setupUi(&widget);
唉,有时干预小部件是不必要的。首先,小部件上的布局具有非零边距,因此如果您打算将 Ui::Form
的内容插入到布局中,则必须重新设置边距:
QWidget top;
QVBoxLayout layout⊤
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⊤
Ui::Form ui;
ui.setupUi(nullptr);
layout.addLayout(ui.layout); // assuming `layout` is the top-level layout there
我检查了uic
,它不支持在没有***小部件的情况下发出代码。确实需要的补丁是微不足道的。
但是,有没有办法在不修补 Qt 的情况下实现 nullptr
-accepting setupUi
的效果?
免得有人认为这是一个虚构的问题:这个问题部分是由this code from YUView 提出的。在那里,传递给setupUi
的parentWidget
已经有一个布局,目的是在setupUi
完成后将表单的布局添加到另一个布局。从setupUi
内部调用的QWidget::setLayout
拒绝设置新布局,发出警告,尽管如此,事情还是发生了。代码很脆弱,因为它依赖于 Qt 代码以正确的方式破坏以产生所需的结果。
【问题讨论】:
这很棒。抱歉,如果这听起来很愚蠢,但是使用带有setContentsMargins(0,0,0,0);
的干预小部件除了不需要小部件这一事实之外还有什么缺点吗?如果这是关于创建中间小部件的开销,那么答案本质上是创建一个临时小部件并采用其布局?所以,我想这与性能无关。那么,在中间小部件中设置它(边距设置为0
)并将这个小部件添加到布局中的缺点是什么?
我问这个是因为我在处理此类问题时通常使用干预小部件方法。
我喜欢项目的层次结构不包括不必要的垃圾。临时小部件可以永久保留并重复使用。关于一个人支付的唯一额外成本是临时包装器布局,它被构建然后立即销毁。这可能是无事生非,哦,好吧。
我并不是说您解决问题的方法有额外的开销。我只是想猜测使用干预小部件方法的缺点。当你的层次结构中没有垃圾时它会更干净,但我认为还有另一个我没有得到的问题。谢谢。
【参考方案1】:
是的。使我们能够这样做的关键观察结果是:
childLayout
可以通过将其父级设为空来从其父级布局中移除:
childLayout->setParent(nullptr);
在将其添加到小部件或具有非空 parentWidget()
的布局时,无父布局将重新设置其子级。
因此,我们需要向表单添加一个额外的***包装布局,然后从中取消目标***布局的父级,并删除该包装。 dumpObjectTree
报告的表单结构为:
QWidget::Form
QVBoxLayout::wrapper
QVBoxLayout::layout
QHBoxLayout::horizontalLayout
QLabel::label
QLabel::label_2
QLabel::label_3
测试用例:
// https://github.com/KubaO/***n/tree/master/questions/layout-take-40497358
#include <QtWidgets>
#include "ui_form.h"
QLayout * takeLayout(QWidget *);
int main(int argc, char ** argv)
QApplication appargc, 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>
【讨论】:
以上是关于如何使用 setupUi 或 uic 输出来创建没有父小部件的独立布局?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用cmake强制UIC在源目录中生成ui_mainwindow.h
PySide6:ui文件(来自Designer或QtCreater)与QUiLoader和pyside6-uic一起使用
如何将Qt Designer 产生的*.UI文件转换成QT可以使用的*.H和*.CPP