【问题标题】:Possible to find children that are of promoted widget class in QtDesigner .ui file?可以在 QtDesigner .ui 文件中找到属于提升的小部件类的子级吗?
【发布时间】:2020-11-16 21:47:09
【问题描述】:

在下面的代码中,我在 QtDesigner 中放置了一些元素,然后是几个空的 QFrame,分别名为 my_widget_01my_widget_02

我已经将这些提升为 MyWidget 类,它基本上只是在 Python 代码中添加了一个模型标签:

现在,我想做的是将这些自定义对象(按名称或按类)作为列表“查找”;但由于某种原因我不能。正如您在下面的代码中看到的,如果我运行self.findChildren(QtWidgets.QFrame),它会找到一堆对象,包括自定义对象——但如果我尝试self.findChildren(MyWidget),则会返回一个空列表。另外,如果你运行self.dumpObjectTree(),输出中会出现MyWidget 类的对象——所以这对我来说有点奇怪,为什么.findChildren 找不到它们。

到目前为止,我发现了这个:

How to find an object by name in pyqt?

您可以使用 QObject::findChild 方法。

因此,这篇文章指出,即使按名称 (.objectName()) 查找,也应使用 findChild

access element from .ui

您不需要使用 findChild(),因为如果您使用 loadUi 或 loadUiType,它将使用 objectName 映射对象。

这是指该帖子中的OP问题,因此我无法确定在这种情况下原则上是否可以使用findChild()findChildren()

无论如何,我不想手动保留名称列表[self.my_widget_01, self.my_widget_02],因为除了 .ui 中存在的小部件之外,可能还有动态添加的小部件 - 所以我真的非常想看看它们按名称正则表达式(例如"my_widget_\d\d")或按类(所以我会查找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: []

【问题讨论】:

    标签: python pyqt5 qt-designer


    【解决方案1】:

    说明:

    问题在于,从 main 实例化的 MyFrame 与促销创建的模块属于不同的模块,这可以通过查看过滤器的输出来观察:

    find QFrame: [... , <test1.MyWidget object at 0x00000000060f9dc0>, ... , <test1.MyWidget object at 0x00000000066841f0>, ...
    

    但是如果你运行 print(MyWidget()) 你会得到:

    <__main__.MyWidget object at 0x7feaf056fa60>
    

    解决办法:

    除此之外,代码可以生成循环导入比提升的类与主文件位于不同的模块中要好。

    ├── 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>]
    

    【讨论】:

    • 太棒了——非常感谢;对我来说很难理解命名空间,所以在这种情况下记录事情的表现真的很棒。再次感谢!
    • 在找到并阅读了一些 stackoverflow.com/q/20686684 之后,刚刚尝试使用这些命名空间 - 我发现了一些东西,我想我应该注意它:如果在 OP/问题 test1.py 中,一个人会写 @ 987654331@ 或 from test1 import MyWidget 紧跟在 class MyWidget 定义之后(如果你之前这样做,它会以 ImportError: cannot import name 'MyWidget' from partially initialized module 'test1' (most likely due to a circular import) 失败),然后 findChildren 成功!因此,它是此出色答案中该方法的一种可能替代方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-20
    • 2011-10-08
    • 1970-01-01
    • 2014-03-12
    • 2021-09-06
    相关资源
    最近更新 更多