PyQt5 - 动态添加小部件到布局
PyQt5 - Adding widget to layout dynamically
我正在尝试创建一个自定义小部件,用户可以在其中动态添加更多 [其他] 自定义小部件到 window。这是我的尝试:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class LoadModelWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(LoadModelWidget, self).__init__(*args, **kwargs)
# ------------------------ GUI Components ------------------------ #
self.setFixedHeight(100)
self._container = QtWidgets.QGroupBox(self)
self._container.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding))
self._container.setFixedHeight(100)
self._container.setFixedWidth(210)
layout = QtWidgets.QGridLayout()
self._model_to_select_label = QtWidgets.QLabel("Selected model")
layout.addWidget(self._model_to_select_label, 0, 0)
self._model_to_select_list = QtWidgets.QComboBox()
layout.addWidget(self._model_to_select_list, 0, 1)
_slice_method_label = QtWidgets.QLabel("Slice method")
layout.addWidget(_slice_method_label, 1, 0)
self._list_of_slicing_method = QtWidgets.QComboBox()
layout.addWidget(self._list_of_slicing_method, 1, 1)
self._bottom_radius_label = QtWidgets.QLabel("Bottom radius")
layout.addWidget(self._bottom_radius_label, 2, 0)
self._bottom_radius_edit = QtWidgets.QLineEdit()
layout.addWidget(self._bottom_radius_edit, 2, 1)
self._top_radius_label = QtWidgets.QLabel("Top radius")
layout.addWidget(self._top_radius_label, 3, 0)
self._top_radius_edit = QtWidgets.QLineEdit()
layout.addWidget(self._top_radius_edit, 3, 1)
self._height_label = QtWidgets.QLabel("Height")
layout.addWidget(self._height_label, 4, 0)
self._height_edit = QtWidgets.QLineEdit()
layout.addWidget(self._height_edit, 4, 1)
self._x_dir_label = QtWidgets.QLabel("X direction")
layout.addWidget(self._x_dir_label, 5, 0)
self._x_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._x_dir_edit, 5, 1)
self._y_dir_label = QtWidgets.QLabel("Y direction")
layout.addWidget(self._y_dir_label, 6, 0)
self._y_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._y_dir_edit, 6, 1)
self._z_dir_label = QtWidgets.QLabel("Z direction")
layout.addWidget(self._z_dir_label, 7, 0)
self._z_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._z_dir_edit, 7, 1)
self._remove_btn = QtWidgets.QPushButton("Remove model")
layout.addWidget(self._remove_btn, 8, 0)
self._bottom_radius_label.setVisible(False)
self._bottom_radius_edit.setVisible(False)
self._top_radius_label.setVisible(False)
self._top_radius_edit.setVisible(False)
self._height_label.setVisible(False)
self._height_edit.setVisible(False)
self._x_dir_label.setVisible(False)
self._x_dir_edit.setVisible(False)
self._y_dir_label.setVisible(False)
self._y_dir_edit.setVisible(False)
self._z_dir_label.setVisible(False)
self._z_dir_edit.setVisible(False)
self._container.setLayout(layout)
# -------------------- Other attributes -------------------- "
self._filename = ""
# -------------------- Assign slot and signal -------------------- "
self._list_of_slicing_method.currentIndexChanged.connect(self.slice_method_changed)
self._remove_btn.clicked.connect(self.remove_model)
def set_model_dir(self, filename, filedir, suggested_slicing_method="Parallel"):
# Assign only the name of the file, not the whole stl model
self._filename = filename
self._filedir = filedir
self._list_of_slicing_method.addItem("Parallel")
self._list_of_slicing_method.addItem("Revolution")
self._list_of_slicing_method.addItem("Radial")
self._list_of_slicing_method.addItem("ParallelCurve")
self._list_of_slicing_method.setCurrentText(suggested_slicing_method)
def get_model_full_dir(self):
return self._filedir + "\" + self._filename
def remove_model(self):
self._filename = ""
self._filedir = ""
self._list_of_slicing_method.clear()
def slice_method_changed(self):
is_parallel = self._list_of_slicing_method.currentText() == "Parallel"
is_radial = self._list_of_slicing_method.currentText() == "Radial"
self._bottom_radius_label.setVisible(is_radial)
self._bottom_radius_edit.setVisible(is_radial)
self._top_radius_label.setVisible(is_radial)
self._top_radius_edit.setVisible(is_radial)
self._height_label.setVisible(is_radial)
self._height_edit.setVisible(is_radial)
self._x_dir_label.setVisible(is_parallel)
self._x_dir_edit.setVisible(is_parallel)
self._y_dir_label.setVisible(is_parallel)
self._y_dir_edit.setVisible(is_parallel)
self._z_dir_label.setVisible(is_parallel)
self._z_dir_edit.setVisible(is_parallel)
padding = 50
minimum_height = 100
new_height = (self._bottom_radius_label.height() + padding)*is_radial + (self._top_radius_edit.height() + padding)*is_radial + (self._height_label.height() + padding)*is_radial + (self._x_dir_label.height() + padding)*is_parallel + (self._y_dir_label.height() + padding)*is_parallel + (self._z_dir_label.height() + padding)*is_parallel
if (new_height > minimum_height):
self._container.setFixedHeight(new_height)
self.setFixedHeight(new_height)
else:
self._container.setFixedHeight(minimum_height)
self.setFixedHeight(minimum_height)
class LoadModelColumn(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(LoadModelColumn, self).__init__(*args, **kwargs)
# ------------------------ GUI Components ------------------------ #
self._layout = QtWidgets.QGridLayout()
self._add_model_btn = QtWidgets.QPushButton("Add model(s)")
self._layout.addWidget(self._add_model_btn, 0, 0)
self._clear_all_btn = QtWidgets.QPushButton("Clear all")
self._layout.addWidget(self._clear_all_btn, 0, 1)
self._all_load_model_widgets = []
self.verticalSpacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding)
self._layout.addItem(self.verticalSpacer)
self._load_model_group = QtWidgets.QGroupBox()
self._load_model_group.setLayout(self._layout)
self._scroll_area = QtWidgets.QScrollArea(self)
self._scroll_area.setWidgetResizable(True)
self._scroll_area.setFixedHeight(600)
self._scroll_area.setFixedWidth(250)
self._scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self._scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self._scroll_area.setWidget(self._load_model_group)
# -------------------- Assign slot and signal -------------------- "
self._clear_all_btn.clicked.connect(self._clear_all_models)
self._add_model_btn.clicked.connect(self._add_models)
def _clear_all_models(self):
for widget in self._all_load_model_widgets:
self._layout.removeWidget(widget)
widget.deleteLater()
self._all_load_model_widgets = []
def _add_models(self):
widget = LoadModelWidget()
widget.set_model_dir("hi", "hello")
self._all_load_model_widgets.append(widget)
self._layout.addWidget(widget, len(self._all_load_model_widgets), 0, 1, 0)
目前,添加小部件时,由于某种原因,它会尝试占据所有滚动区域。但显然,我真的不想那样,因为我希望它只分配给必要的 space。
下面是用户按下“添加模型”按钮时正在执行的操作的示例:
但这就是我想要的:
您正在将小部件添加到顶级 window 小部件,但您没有为它们设置布局。
您的问题的简单答案是正确设置您正在创建的小部件的布局。在 LoadModelWidget
的 __init__
底部添加以下内容:
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self._container)
底部是 LoadModelColumn
的 __init__
:
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self._scroll_area)
然后删除所有不必要的setFixedHeight
,这没有任何意义。
我强烈建议您更好地研究layout manager work,因为所有需要调整其内容的小部件也需要您为其设置布局。
然后我也建议你更好地阐明你的 classes 的对象结构。例如,没有必要从 QWidget subclass LoadModelWidget
,如果它总是包含一个单独的组框:只需 subclass QGroupBox.
作为旁注(但仍然非常重要),请始终尝试使示例尽可能少(阅读有关创建 minimal, reproducible examples 的更多信息(您甚至可能最终会发现自己的解决方案);另外,这也非常重要,请始终努力使这些示例 立即可重现 :从您的代码来看, 不是 清楚 class 被用作父项),你的例子很长;我们必须能够复制和粘贴您的代码,并运行尽可能不费力:这意味着我们希望帮助你,但你必须帮助我们帮助你。
我正在尝试创建一个自定义小部件,用户可以在其中动态添加更多 [其他] 自定义小部件到 window。这是我的尝试:
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
class LoadModelWidget(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(LoadModelWidget, self).__init__(*args, **kwargs)
# ------------------------ GUI Components ------------------------ #
self.setFixedHeight(100)
self._container = QtWidgets.QGroupBox(self)
self._container.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding))
self._container.setFixedHeight(100)
self._container.setFixedWidth(210)
layout = QtWidgets.QGridLayout()
self._model_to_select_label = QtWidgets.QLabel("Selected model")
layout.addWidget(self._model_to_select_label, 0, 0)
self._model_to_select_list = QtWidgets.QComboBox()
layout.addWidget(self._model_to_select_list, 0, 1)
_slice_method_label = QtWidgets.QLabel("Slice method")
layout.addWidget(_slice_method_label, 1, 0)
self._list_of_slicing_method = QtWidgets.QComboBox()
layout.addWidget(self._list_of_slicing_method, 1, 1)
self._bottom_radius_label = QtWidgets.QLabel("Bottom radius")
layout.addWidget(self._bottom_radius_label, 2, 0)
self._bottom_radius_edit = QtWidgets.QLineEdit()
layout.addWidget(self._bottom_radius_edit, 2, 1)
self._top_radius_label = QtWidgets.QLabel("Top radius")
layout.addWidget(self._top_radius_label, 3, 0)
self._top_radius_edit = QtWidgets.QLineEdit()
layout.addWidget(self._top_radius_edit, 3, 1)
self._height_label = QtWidgets.QLabel("Height")
layout.addWidget(self._height_label, 4, 0)
self._height_edit = QtWidgets.QLineEdit()
layout.addWidget(self._height_edit, 4, 1)
self._x_dir_label = QtWidgets.QLabel("X direction")
layout.addWidget(self._x_dir_label, 5, 0)
self._x_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._x_dir_edit, 5, 1)
self._y_dir_label = QtWidgets.QLabel("Y direction")
layout.addWidget(self._y_dir_label, 6, 0)
self._y_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._y_dir_edit, 6, 1)
self._z_dir_label = QtWidgets.QLabel("Z direction")
layout.addWidget(self._z_dir_label, 7, 0)
self._z_dir_edit = QtWidgets.QLineEdit()
layout.addWidget(self._z_dir_edit, 7, 1)
self._remove_btn = QtWidgets.QPushButton("Remove model")
layout.addWidget(self._remove_btn, 8, 0)
self._bottom_radius_label.setVisible(False)
self._bottom_radius_edit.setVisible(False)
self._top_radius_label.setVisible(False)
self._top_radius_edit.setVisible(False)
self._height_label.setVisible(False)
self._height_edit.setVisible(False)
self._x_dir_label.setVisible(False)
self._x_dir_edit.setVisible(False)
self._y_dir_label.setVisible(False)
self._y_dir_edit.setVisible(False)
self._z_dir_label.setVisible(False)
self._z_dir_edit.setVisible(False)
self._container.setLayout(layout)
# -------------------- Other attributes -------------------- "
self._filename = ""
# -------------------- Assign slot and signal -------------------- "
self._list_of_slicing_method.currentIndexChanged.connect(self.slice_method_changed)
self._remove_btn.clicked.connect(self.remove_model)
def set_model_dir(self, filename, filedir, suggested_slicing_method="Parallel"):
# Assign only the name of the file, not the whole stl model
self._filename = filename
self._filedir = filedir
self._list_of_slicing_method.addItem("Parallel")
self._list_of_slicing_method.addItem("Revolution")
self._list_of_slicing_method.addItem("Radial")
self._list_of_slicing_method.addItem("ParallelCurve")
self._list_of_slicing_method.setCurrentText(suggested_slicing_method)
def get_model_full_dir(self):
return self._filedir + "\" + self._filename
def remove_model(self):
self._filename = ""
self._filedir = ""
self._list_of_slicing_method.clear()
def slice_method_changed(self):
is_parallel = self._list_of_slicing_method.currentText() == "Parallel"
is_radial = self._list_of_slicing_method.currentText() == "Radial"
self._bottom_radius_label.setVisible(is_radial)
self._bottom_radius_edit.setVisible(is_radial)
self._top_radius_label.setVisible(is_radial)
self._top_radius_edit.setVisible(is_radial)
self._height_label.setVisible(is_radial)
self._height_edit.setVisible(is_radial)
self._x_dir_label.setVisible(is_parallel)
self._x_dir_edit.setVisible(is_parallel)
self._y_dir_label.setVisible(is_parallel)
self._y_dir_edit.setVisible(is_parallel)
self._z_dir_label.setVisible(is_parallel)
self._z_dir_edit.setVisible(is_parallel)
padding = 50
minimum_height = 100
new_height = (self._bottom_radius_label.height() + padding)*is_radial + (self._top_radius_edit.height() + padding)*is_radial + (self._height_label.height() + padding)*is_radial + (self._x_dir_label.height() + padding)*is_parallel + (self._y_dir_label.height() + padding)*is_parallel + (self._z_dir_label.height() + padding)*is_parallel
if (new_height > minimum_height):
self._container.setFixedHeight(new_height)
self.setFixedHeight(new_height)
else:
self._container.setFixedHeight(minimum_height)
self.setFixedHeight(minimum_height)
class LoadModelColumn(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super(LoadModelColumn, self).__init__(*args, **kwargs)
# ------------------------ GUI Components ------------------------ #
self._layout = QtWidgets.QGridLayout()
self._add_model_btn = QtWidgets.QPushButton("Add model(s)")
self._layout.addWidget(self._add_model_btn, 0, 0)
self._clear_all_btn = QtWidgets.QPushButton("Clear all")
self._layout.addWidget(self._clear_all_btn, 0, 1)
self._all_load_model_widgets = []
self.verticalSpacer = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.MinimumExpanding)
self._layout.addItem(self.verticalSpacer)
self._load_model_group = QtWidgets.QGroupBox()
self._load_model_group.setLayout(self._layout)
self._scroll_area = QtWidgets.QScrollArea(self)
self._scroll_area.setWidgetResizable(True)
self._scroll_area.setFixedHeight(600)
self._scroll_area.setFixedWidth(250)
self._scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self._scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self._scroll_area.setWidget(self._load_model_group)
# -------------------- Assign slot and signal -------------------- "
self._clear_all_btn.clicked.connect(self._clear_all_models)
self._add_model_btn.clicked.connect(self._add_models)
def _clear_all_models(self):
for widget in self._all_load_model_widgets:
self._layout.removeWidget(widget)
widget.deleteLater()
self._all_load_model_widgets = []
def _add_models(self):
widget = LoadModelWidget()
widget.set_model_dir("hi", "hello")
self._all_load_model_widgets.append(widget)
self._layout.addWidget(widget, len(self._all_load_model_widgets), 0, 1, 0)
目前,添加小部件时,由于某种原因,它会尝试占据所有滚动区域。但显然,我真的不想那样,因为我希望它只分配给必要的 space。
下面是用户按下“添加模型”按钮时正在执行的操作的示例:
但这就是我想要的:
您正在将小部件添加到顶级 window 小部件,但您没有为它们设置布局。
您的问题的简单答案是正确设置您正在创建的小部件的布局。在 LoadModelWidget
的 __init__
底部添加以下内容:
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self._container)
底部是 LoadModelColumn
的 __init__
:
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self._scroll_area)
然后删除所有不必要的setFixedHeight
,这没有任何意义。
我强烈建议您更好地研究layout manager work,因为所有需要调整其内容的小部件也需要您为其设置布局。
然后我也建议你更好地阐明你的 classes 的对象结构。例如,没有必要从 QWidget subclass LoadModelWidget
,如果它总是包含一个单独的组框:只需 subclass QGroupBox.
作为旁注(但仍然非常重要),请始终尝试使示例尽可能少(阅读有关创建 minimal, reproducible examples 的更多信息(您甚至可能最终会发现自己的解决方案);另外,这也非常重要,请始终努力使这些示例 立即可重现 :从您的代码来看, 不是 清楚 class 被用作父项),你的例子很长;我们必须能够复制和粘贴您的代码,并运行尽可能不费力:这意味着我们希望帮助你,但你必须帮助我们帮助你。