可以在 QtDesigner .ui 文件中找到属于升级小部件 class 的子项吗?
Possible to find children that are of promoted widget class in QtDesigner .ui file?
在下面的代码中,我在 QtDesigner 中放置了一些元素,然后是几个空的 QFrames,名为 my_widget_01
和 my_widget_02
:
我已将它们提升为 MyWidget class,它基本上只是在 Python 代码中添加了一个模型标签:
现在,我想做的是“查找”这些自定义对象 - 按名称或按 class - 作为列表;但出于某种原因我不能。正如您在下面的代码中看到的,如果我 运行 self.findChildren(QtWidgets.QFrame)
它会找到一堆对象,包括自定义对象 - 但如果我尝试 self.findChildren(MyWidget)
,则会返回一个空列表。此外,如果你 运行 self.dumpObjectTree()
,输出中会出现 class MyWidget
的对象 - 所以这对我来说有点奇怪,为什么不能 .findChildren
找到他们。
到目前为止我发现了这个:
How to find an object by name in pyqt?
You can use QObject::findChild method.
因此,此 post 说明,即使按名称查找 (.objectName()
),也应使用 findChild
。
You don't need to use findChild() since if you use loadUi or loadUiType it will map the objects using the objectName.
这里指的是那个post中的OP问题,所以原则上我无法判断这种情况下是否可以使用findChild()
或findChildren()
。
在任何情况下,我都不想手动保留名称列表 [self.my_widget_01, self.my_widget_02]
,因为除了 .ui 中存在的那些之外,可能还有动态添加的小部件 - 所以我'我真的非常喜欢通过名称正则表达式(例如 "my_widget_\d\d"
)或 class(所以我会查找 MyWidget
)来查找它们;在这种情况下没关系,因为我会将所有 MyWidget
个小部件命名为 my_widget_XY
。我需要这个以便我可以遍历它们,无论它们最终出现在 GUI 中有多少。
是否可以在 PyQt5 中执行此操作(获取所有升级的 MyWidget
小部件的列表,无论它们是否存在于 .ui 文件中或动态添加)- 如果可以,怎么样?
test1.ui
:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>302</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSplitter" name="splitter">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>558</width>
<height>192</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QTreeView" name="treeView"/>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="MyWidget" name="my_widget_02">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="MyWidget" name="my_widget_01">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>MyWidget</class>
<extends>QFrame</extends>
<header>test1</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
test1.py
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
from PyQt5.QtCore import pyqtSlot
class MyWidget(QtWidgets.QFrame):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.hlay = QtWidgets.QHBoxLayout(self)
self.hlay.setContentsMargins(1, 1, 1, 1)
self.label = QtWidgets.QLabel(text="{}".format(self))
self.hlay.addWidget(self.label, 0, QtCore.Qt.AlignVCenter)
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test1.ui', self)
self.dumpObjectTree() # debug, auto-prints to stdout
print("find QFrame:", self.findChildren(QtWidgets.QFrame)) # find QFrame: [<PyQt5.QtWidgets.QSplitter object at 0x00000000060f8a60>, <PyQt5.QtWidgets.QFrame object at 0x00000000060f8c10>, <test1.MyWidget object at 0x00000000060f8d30> ...
print("find MyWidget:", self.findChildren(MyWidget)) # []
self.show()
print("after show")
print("find MyWidget:", self.findChildren(MyWidget)) # []
QtCore.QTimer.singleShot(1, self.delayed_init) # run after 1 ms
def delayed_init(self):
print("delayed MyWidget:", self.findChildren(MyWidget)) # []
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
终端输出:
$ python3 /tmp/test1.py
MyMainWindow::MainWindow
QMainWindowLayout::_layout
QWidget::centralwidget
QSplitter::splitter
QFrame::frame
QHBoxLayout::horizontalLayout
MyWidget::my_widget_02
QHBoxLayout::
QLabel::
MyWidget::my_widget_01
QHBoxLayout::
QLabel::
QTreeView::treeView
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QStyledItemDelegate::
QHeaderView::
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QListView::listView
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QStyledItemDelegate::
QSplitterHandle::qt_splithandle_
QSplitterHandle::qt_splithandle_
QSplitterHandle::qt_splithandle_
QMenuBar::menubar
QToolButton::qt_menubar_ext_button
QStatusBar::statusbar
QSizeGrip::
QHBoxLayout::
QVBoxLayout::
QHBoxLayout::
find QFrame: [<PyQt5.QtWidgets.QSplitter object at 0x00000000060f9af0>, <PyQt5.QtWidgets.QFrame object at 0x00000000060f9ca0>, <test1.MyWidget object at 0x00000000060f9dc0>, <PyQt5.QtWidgets.QLabel object at 0x0000000006684160>, <test1.MyWidget object at 0x00000000066841f0>, <PyQt5.QtWidgets.QLabel object at 0x0000000006684310>, <PyQt5.QtWidgets.QTreeView object at 0x00000000060f9c10>, <PyQt5.QtWidgets.QHeaderView object at 0x00000000066844c0>, <PyQt5.QtWidgets.QListView object at 0x00000000060f9b80>]
find MyWidget: []
after show
find MyWidget: []
delayed MyWidget: []
解释:
问题在于,从 main 实例化的 MyFrame 属于与提升创建的模块不同的模块,可以通过查看过滤器的输出来观察:
find QFrame: [... , <test1.MyWidget object at 0x00000000060f9dc0>, ... , <test1.MyWidget object at 0x00000000066841f0>, ...
但是如果你 运行 print(MyWidget())
你会得到:
<__main__.MyWidget object at 0x7feaf056fa60>
解决方案:
除此之外,代码可以生成循环导入比提升的更好 class 与主文件位于不同的模块中。
├── mywidget.py
├── test1.py
└── test1.ui
mywidget.py
from PyQt5 import QtCore, QtWidgets
class MyWidget(QtWidgets.QFrame):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.hlay = QtWidgets.QHBoxLayout(self)
self.hlay.setContentsMargins(1, 1, 1, 1)
self.label = QtWidgets.QLabel(text="{}".format(self))
self.hlay.addWidget(self.label, 0, QtCore.Qt.AlignVCenter)
test1.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>302</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSplitter" name="splitter">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>558</width>
<height>192</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QTreeView" name="treeView"/>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="MyWidget" name="my_widget_02">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="MyWidget" name="my_widget_01">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>MyWidget</class>
<extends>QFrame</extends>
<header>mywidget</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
test1.py
import sys
from PyQt5 import QtCore, QtWidgets, uic
from mywidget import MyWidget
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi("test1.ui", self)
print("find MyWidget:", self.findChildren(MyWidget))
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
输出:
find MyWidget: [<mywidget.MyWidget object at 0x7f8d2c01e280>, <mywidget.MyWidget object at 0x7f8d2c01e430>]
在下面的代码中,我在 QtDesigner 中放置了一些元素,然后是几个空的 QFrames,名为 my_widget_01
和 my_widget_02
:
我已将它们提升为 MyWidget class,它基本上只是在 Python 代码中添加了一个模型标签:
现在,我想做的是“查找”这些自定义对象 - 按名称或按 class - 作为列表;但出于某种原因我不能。正如您在下面的代码中看到的,如果我 运行 self.findChildren(QtWidgets.QFrame)
它会找到一堆对象,包括自定义对象 - 但如果我尝试 self.findChildren(MyWidget)
,则会返回一个空列表。此外,如果你 运行 self.dumpObjectTree()
,输出中会出现 class MyWidget
的对象 - 所以这对我来说有点奇怪,为什么不能 .findChildren
找到他们。
到目前为止我发现了这个:
How to find an object by name in pyqt?
You can use QObject::findChild method.
因此,此 post 说明,即使按名称查找 (.objectName()
),也应使用 findChild
。
You don't need to use findChild() since if you use loadUi or loadUiType it will map the objects using the objectName.
这里指的是那个post中的OP问题,所以原则上我无法判断这种情况下是否可以使用findChild()
或findChildren()
。
在任何情况下,我都不想手动保留名称列表 [self.my_widget_01, self.my_widget_02]
,因为除了 .ui 中存在的那些之外,可能还有动态添加的小部件 - 所以我'我真的非常喜欢通过名称正则表达式(例如 "my_widget_\d\d"
)或 class(所以我会查找 MyWidget
)来查找它们;在这种情况下没关系,因为我会将所有 MyWidget
个小部件命名为 my_widget_XY
。我需要这个以便我可以遍历它们,无论它们最终出现在 GUI 中有多少。
是否可以在 PyQt5 中执行此操作(获取所有升级的 MyWidget
小部件的列表,无论它们是否存在于 .ui 文件中或动态添加)- 如果可以,怎么样?
test1.ui
:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>302</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSplitter" name="splitter">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>558</width>
<height>192</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QTreeView" name="treeView"/>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="MyWidget" name="my_widget_02">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="MyWidget" name="my_widget_01">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>MyWidget</class>
<extends>QFrame</extends>
<header>test1</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
test1.py
import sys
from PyQt5 import QtCore, QtWidgets, QtGui, uic
from PyQt5.QtCore import pyqtSlot
class MyWidget(QtWidgets.QFrame):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.hlay = QtWidgets.QHBoxLayout(self)
self.hlay.setContentsMargins(1, 1, 1, 1)
self.label = QtWidgets.QLabel(text="{}".format(self))
self.hlay.addWidget(self.label, 0, QtCore.Qt.AlignVCenter)
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi('test1.ui', self)
self.dumpObjectTree() # debug, auto-prints to stdout
print("find QFrame:", self.findChildren(QtWidgets.QFrame)) # find QFrame: [<PyQt5.QtWidgets.QSplitter object at 0x00000000060f8a60>, <PyQt5.QtWidgets.QFrame object at 0x00000000060f8c10>, <test1.MyWidget object at 0x00000000060f8d30> ...
print("find MyWidget:", self.findChildren(MyWidget)) # []
self.show()
print("after show")
print("find MyWidget:", self.findChildren(MyWidget)) # []
QtCore.QTimer.singleShot(1, self.delayed_init) # run after 1 ms
def delayed_init(self):
print("delayed MyWidget:", self.findChildren(MyWidget)) # []
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
终端输出:
$ python3 /tmp/test1.py
MyMainWindow::MainWindow
QMainWindowLayout::_layout
QWidget::centralwidget
QSplitter::splitter
QFrame::frame
QHBoxLayout::horizontalLayout
MyWidget::my_widget_02
QHBoxLayout::
QLabel::
MyWidget::my_widget_01
QHBoxLayout::
QLabel::
QTreeView::treeView
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QStyledItemDelegate::
QHeaderView::
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QListView::listView
QWidget::qt_scrollarea_viewport
QWidget::qt_scrollarea_hcontainer
QScrollBar::
QBoxLayout::
QWidget::qt_scrollarea_vcontainer
QScrollBar::
QBoxLayout::
QStyledItemDelegate::
QSplitterHandle::qt_splithandle_
QSplitterHandle::qt_splithandle_
QSplitterHandle::qt_splithandle_
QMenuBar::menubar
QToolButton::qt_menubar_ext_button
QStatusBar::statusbar
QSizeGrip::
QHBoxLayout::
QVBoxLayout::
QHBoxLayout::
find QFrame: [<PyQt5.QtWidgets.QSplitter object at 0x00000000060f9af0>, <PyQt5.QtWidgets.QFrame object at 0x00000000060f9ca0>, <test1.MyWidget object at 0x00000000060f9dc0>, <PyQt5.QtWidgets.QLabel object at 0x0000000006684160>, <test1.MyWidget object at 0x00000000066841f0>, <PyQt5.QtWidgets.QLabel object at 0x0000000006684310>, <PyQt5.QtWidgets.QTreeView object at 0x00000000060f9c10>, <PyQt5.QtWidgets.QHeaderView object at 0x00000000066844c0>, <PyQt5.QtWidgets.QListView object at 0x00000000060f9b80>]
find MyWidget: []
after show
find MyWidget: []
delayed MyWidget: []
解释:
问题在于,从 main 实例化的 MyFrame 属于与提升创建的模块不同的模块,可以通过查看过滤器的输出来观察:
find QFrame: [... , <test1.MyWidget object at 0x00000000060f9dc0>, ... , <test1.MyWidget object at 0x00000000066841f0>, ...
但是如果你 运行 print(MyWidget())
你会得到:
<__main__.MyWidget object at 0x7feaf056fa60>
解决方案:
除此之外,代码可以生成循环导入比提升的更好 class 与主文件位于不同的模块中。
├── mywidget.py
├── test1.py
└── test1.ui
mywidget.py
from PyQt5 import QtCore, QtWidgets
class MyWidget(QtWidgets.QFrame):
def __init__(self, *args, **kwargs):
super(MyWidget, self).__init__(*args, **kwargs)
self.hlay = QtWidgets.QHBoxLayout(self)
self.hlay.setContentsMargins(1, 1, 1, 1)
self.label = QtWidgets.QLabel(text="{}".format(self))
self.hlay.addWidget(self.label, 0, QtCore.Qt.AlignVCenter)
test1.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>302</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QSplitter" name="splitter">
<property name="geometry">
<rect>
<x>30</x>
<y>40</y>
<width>558</width>
<height>192</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QListView" name="listView"/>
<widget class="QTreeView" name="treeView"/>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="MyWidget" name="my_widget_02">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="MyWidget" name="my_widget_01">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>589</width>
<height>26</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<customwidgets>
<customwidget>
<class>MyWidget</class>
<extends>QFrame</extends>
<header>mywidget</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
test1.py
import sys
from PyQt5 import QtCore, QtWidgets, uic
from mywidget import MyWidget
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
uic.loadUi("test1.ui", self)
print("find MyWidget:", self.findChildren(MyWidget))
def main():
app = QtWidgets.QApplication(sys.argv)
window = MyMainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
输出:
find MyWidget: [<mywidget.MyWidget object at 0x7f8d2c01e280>, <mywidget.MyWidget object at 0x7f8d2c01e430>]