PyQt6 获取 QTreeWidgetItem 的父 window?

PyQt6 get the parent window of QTreeWidgetItem?

我在 Python 3.9.6 上使用 PyQt6 6.1.0,我想获取 QTreeWidgetItem 的父 QTreeWidget 的父 QMainWindow 的父 QTreeWidgetItems,因为我需要修改 window 来自 QTreeWidgetItem.

完整代码很长,超过1800行,大小超过60KiB,当我把它放在一个文件中时,它没有问题,但是当放在一个文件中时,它的可读性很低,当拆分成文件时全局变量很难从 classes.

中访问

基本上我的想法是重复调用 .parent() 直到对象的 class 名称为 'MainWindow'.

def getWindow(obj):
    window = obj.parent()
    while True:
        if type(window).__name__ == 'MainWindow':
            break
        window = window.parent()
    return window

我没有直接使用class因为class是在主文件中定义的

这是在名为 groupA.py 的文件中定义的两个 class:

class TreeNode(QTreeWidgetItem):
    def __init__(self, texts):
        super().__init__()
        self.isSongNode = False
        self.setFlags(
            FLAG.ItemIsAutoTristate
            | FLAG.ItemIsEnabled
            | FLAG.ItemIsSelectable
            | FLAG.ItemIsUserCheckable
        )
        self.setCheckState(0, Qt.CheckState.Unchecked)
        
        self.data = dict(zip(fields, texts))
        
        self.song = None
        
        for i in ("title", "album", "artist"):
            if i in self.data:
                if i == "title":
                    self.setTextAlignment(0, Qt.AlignmentFlag.AlignLeft)
                    self.setText(0, self.data["title"])
                    self.setToolTip(0, self.data["title"])
                    self.setText(1, self.data["duration"])
                    self.setText(2, self.data["instrumental"])
                    self.setText(3, self.data["downloaded"])
                    self.setText(4, ",".join(self.data["language"]))
                    self.setText(5, self.data["artistgender"])
                    for j in range(1, 6):
                        self.setTextAlignment(j, Qt.AlignmentFlag.AlignCenter)
                        self.song = song(*[self.data[i] for i in cells])
                    self.isSongNode = True
                else:
                    self.setText(0, self.data[i])
                    self.setToolTip(0, self.data[i])
                break
    
    def detail(self):
        print(self)
        print(self.parent())
        print(self.parent().parent())
        print(self.parent().parent().parent())
        window = getWindow(self)
        if self.childCount() != 0:
            for i in range(self.childCount()):
                self.child(i).detail()
        else:
            if self.song not in detailed_items and self.isSongNode:
                widget = SongPage(self.data)
                window.detailArea.scrollArea.Layout.addWidget(widget)
                widget.autoResize()
                detailed_items.append(self.song)


class Tree(QTreeWidget):
    def __init__(self):
        super().__init__()
        self.init()
    
    def init(self):
        self.setColumnCount(len(HEADERS))
        self.setHeaderLabels(HEADERS)
        font = Font(9)
        self.setFont(font)
        self.header().setFont(font)
        self.setColumnWidth(0, 900)
        fontRuler = QFontMetrics(font)
        for i in range(1, len(HEADERS)):
            Width = fontRuler.size(0, HEADERS[i]).width() + 16
            self.setColumnWidth(i, Width)
        self.setAutoScroll(True)
        self.setIndentation(32)
        self.setAlternatingRowColors(True)
        self.setUniformRowHeights(True)
        self.itemClicked.connect(self.onItemClicked)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
    
    @pyqtSlot(QTreeWidgetItem)
    def onItemClicked(self, item):
        window = getWindow(self)
        nodes = self.findItems(
            "", Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchRecursive
        )
        for node in nodes:
            if (
                node.checkState(0) == Qt.CheckState.Unchecked
                and node.song in detailed_items
            ):
                i = detailed_items.index(node.song)
                detailed_items.remove(node.song)
                layoutitem = window.detailArea.scrollArea.Layout.itemAt(i)
                widget = layoutitem.widget()
                window.detailArea.scrollArea.Layout.removeItem(layoutitem)
                window.detailArea.scrollArea.Layout.removeWidget(widget)
            elif (
                node.childCount() == 0
                and node.checkState(0) == Qt.CheckState.Checked
                and node.isSongNode
            ):
                node.detail()
        if item.childCount() == 0 and item.isSongNode:
            if item.song not in detailed_items:
                item.detail()
            
            index = detailed_items.index(item.song)
            dim_others(window, index)
            widget = window.detailArea.scrollArea.Layout.itemAt(index).widget()
            QTimer.singleShot(
                25, lambda: window.detailArea.scrollArea.ensureWidgetVisible(
                    widget)
            )
            widget.highlight()
        setButtonsStatus(window)

树是这样填充的:

def add_nodes(tree, cls, data):
    indexes.clear()
    for artist, albums in data.items():
        artist_node = cls([artist])
        indexes.add([artist])
        for album, songs in albums.items():
            album_node = cls([artist, album])
            indexes.add([artist, album])
            for song in songs:
                song_node = cls([artist, album, *song])
                indexes.add([artist, album, song[0]])
                album_node.addChild(song_node)
            artist_node.addChild(album_node)
        tree.addTopLevelItem(artist_node)

这是代码执行的开始:

if __name__ == '__main__':
    mysql80 = psutil.win_service_get("MySQL80").as_dict()
    if mysql80["status"] != "running":
        os.system("net start MySQL80")
    
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(u'Asceteria')
    songs = fetchdata()
    entries = treefy(songs)
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    tree = Tree()
    add_nodes(tree, TreeNode, entries)
    filterArea = FilterArea()
    Window = MainWindow()
    print(tree)
    print(tree.parent())
    print(tree.parent().parent())
    Window.showMaximized()
    app.setWindowIcon(QIcon(ICON))
    app.exec()

当我检查任何 QTreeWidgetItem 时,程序崩溃,因为顶级 QTreeWidgetItem 没有父级:

<groupA.Tree object at 0x0000022ABCFE9430>
<PyQt6.QtWidgets.QWidget object at 0x0000022AC10DBAF0>
<__main__.MainWindow object at 0x0000022AC10DBA60>
<groupA.TreeNode object at 0x0000022ABE087EE0>
<groupA.TreeNode object at 0x0000022ABE087E50>
<groupA.TreeNode object at 0x0000022ABE087DC0>
None
Traceback (most recent call last):
  File "D:\Asceteria\groupA.py", line 148, in onItemClicked
    node.detail()
  File "D:\Asceteria\groupA.py", line 90, in detail
    window = getWindow(self)
  File "D:\Asceteria\functions.py", line 112, in getWindow
    window = window.parent()
AttributeError: 'NoneType' object has no attribute 'parent'

我该如何解决这个问题?

不需要实现任何方法来获取与小部件关联的 window,因为 Qt 为其提供了 window() 方法:

QWidget *QWidget::window()
const Returns the window for this widget, i.e. the next ancestor widget that has (or could have) a window-system frame.

If the widget is a window, the widget itself is returned.

Typical usage is changing the window title:

aWidget->window()->setWindowTitle("New Window Title");

在你的情况下使用:

window = self.treeWidget().window()

注意:如果要验证对象是 class 或其祖先的实例,则必须使用 isinstace:

isinstance(window, MainWindow)

注意:QTreeWidgetItem与QWidgets的关系不同