简体   繁体   中英

Possible to find children that are of promoted widget class in QtDesigner .ui file?

In the code below, I have some elements placed in QtDesigner, and then a couple of empty QFrames, named my_widget_01 , and my_widget_02 :

qtdesign-mywidgets

I have promoted these to a MyWidget class, which basically just adds a mock-up label in the Python code:

python-gui-mmywidgets

Now, what I would like to do, is to "find" these custom objects - either by name, or by class - as a list; but for some reason I can't. As you can see in the code below, if I run self.findChildren(QtWidgets.QFrame) it finds a bunch of objects, including the custom ones - but if I try self.findChildren(MyWidget) , an empty list is returned. Also, if you run self.dumpObjectTree() , there are objects of class MyWidget present in the output - so it is a bit strange for me, why cannot .findChildren find them.

I have so far found this:

How to find an object by name in pyqt?

You can use QObject::findChild method.

So, this post notes, that even if looking up by name ( .objectName() ), findChild should be used.

access element from.ui

You don't need to use findChild() since if you use loadUi or loadUiType it will map the objects using the objectName.

This refers to the OP problem in that post, so I could not tell if it is possible or not to use findChild() or findChildren() in such a case in principle.

In any case, I do not want to manually keep a list of names [self.my_widget_01, self.my_widget_02] , because there may be dynamically added widgets in addition to those present in the.ui - so I'd really, really like to look them up by a name regex (for instance "my_widget_\d\d" ) - or by class (so I'd look up MyWidget ); it does not matter in this case, as I'd keep all MyWidget widgets named as my_widget_XY . I need this so that I could loop over them, regardless of how many they end up being in the GUI.

Is this (getting a list of all promoted MyWidget widgets, regardless if they are present in the.ui file, or added dynamically) possible to do in PyQt5 - and if so, how?

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()

Output in terminal:

$ 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: []

Explanation:

The problem is that the MyFrame instantiated from main belongs to a different module to the one created by the promotion, and that can be observed by looking at the output of the filter:

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

But if you run print(MyWidget()) you get:

<__main__.MyWidget object at 0x7feaf056fa60>

Solution:

For the above and in addition that the code can generate circular imports is better than the promoted class is in a different module than the main file.

├── 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()

Output:

find MyWidget: [<mywidget.MyWidget object at 0x7f8d2c01e280>, <mywidget.MyWidget object at 0x7f8d2c01e430>]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM