Python PyQt6 заполняет QTreeWidget списком именованных кортежей

Это скрипт Python 3.9, который извлекает данные из базы данных MySQL и преобразует их в список именованных кортежей, а затем передает их в PyQt6.QTreeWidget.

Это должно быть list из namedtuples, потому что я собираюсь хранить списки тегов, упакованных в строки, выгруженные из json.dumps в отдельных полях, затем используя json.loads распаковать списки. Таким образом, фильтрацию по тегам можно легко выполнить с помощью issubsetметод.

Это будет основной интерфейс проекта музыкального проигрывателя с графическим интерфейсом пользователя, над которым я работаю, хотя он все еще остается голым, по крайней мере, часть иерархии дерева завершена.

Это проблеск базы данных:

введите описание изображения здесь

Вот как выглядит окно:

введите описание изображения здесь

А вот код:

import sys
import mysql.connector
from collections import namedtuple
from PyQt6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem

song = namedtuple('song', 'artist title album')

app = QApplication(sys.argv)
conn = mysql.connector.connect(user="Estranger", password=********, host="127.0.0.1", port=3306, database="Music")
cursor = conn.cursor()
cursor.execute('select artist, title, album from songs')
songs = [song(*i) for i in cursor.fetchall()]
conn.commit()
tree = QTreeWidget()
tree.resize(1280,720)
tree.setWindowTitle('tree')
frame = tree.frameGeometry()
center = tree.screen().availableGeometry().center()
frame.moveCenter(center)
tree.move(frame.topLeft())
tree.setColumnCount(4)
tree.setHeaderLabels(['Name', 'Artist', 'Album', 'Title'])
entries = []
for p in sorted(set([_.artist for _ in songs])):
    artist = QTreeWidgetItem([p])
    for a in sorted(set([_.album for _ in songs if _.artist == p])):
        album = QTreeWidgetItem([a, p, a])
        for s in [_.title for _ in songs if _.artist == p and _.album == a]:
            song = QTreeWidgetItem([s, p, a, s])
            album.addChild(song)
        artist.addChild(album)
    entries.append(artist)
tree.insertTopLevelItems(0, entries)
tree.show()
sys.exit(app.exec())

(Пароль скрыт по очевидным причинам)

Я адаптировал свой код отсюда: https://doc.qt.io/qtforpython-6/tutorials/basictutorial/treewidget.html

Код там:

import sys
from PySide6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem
data = {"Project A": ["file_a.py", "file_a.txt", "something.xls"],
        "Project B": ["file_b.csv", "photo.jpg"],
        "Project C": []}
app = QApplication()
tree = QTreeWidget()
tree.setColumnCount(2)
tree.setHeaderLabels(["Name", "Type"])
items = []
for key, values in data.items():
    item = QTreeWidgetItem([key])
    for value in values:
        ext = value.split(".")[-1].upper()
        child = QTreeWidgetItem([value, ext])
        item.addChild(child)
    items.append(item)
tree.insertTopLevelItems(0, items)
tree.show()
sys.exit(app.exec())

В этом примере данные уже являются иерархическими, это словарь, структурированный как дерево, но мои данные представляют собой список именованных кортежей, который является довольно плоским и больше подходит для таблицы.

Но я предпочитаю использовать древовидное представление для представления данных.

Поскольку мои данные плоские, а структура кода примера требует, чтобы данные были иерархическими, я использовал понимание списков плюс приведение к set плюс sorted функция для создания иерархии на лету, которая порождает много накладных расходов, что глупо.

Я действительно новичок в этом, это моя первая попытка создать программу с графическим интерфейсом, я искал в Google несколько дней и не нашел способа заполнить QTreeView или же QTreeWidget с данными из плоского списка, поэтому я написал свой собственный.

Мой вопрос: каков более умный и эффективный способ достичь того же результата (заполнить древовидную структуру данными из списка именованных кортежей) и как я могу использовать QTreeView вместо QTreeWidget(есть ли разница)?

Любая помощь приветствуется.

1 ответ
1

  • У вас есть куча неструктурированного глобального кода; это должно быть организовано в функции и, возможно, классы
  • Закройте курсор, как только вы закончите с ним, в идеале с помощью управления контекстом
  • Упорядочивайте свой запрос, используя саму базу данных, чтобы сделать группировку менее болезненной; также запрос в порядке иерархии (исполнитель, альбом, песня)
  • Тебе не нужно entries; просто добавляйте элементы по одному
  • 4 не должно быть жестко запрограммировано
  • Ваши повторяющиеся представления о множестве можно значительно упростить (то есть отменить) с помощью itertools.groupby

Этот пример кода локализует импорт MySQL, потому что у меня его нет; Вместо этого я создал поддельные данные.

import sys
from dataclasses import dataclass
from itertools import groupby
from typing import Iterable, Callable

from PyQt6.QtWidgets import QApplication, QTreeWidget, QTreeWidgetItem


@dataclass
class Song:
    artist: str
    album: str
    title: str

    @classmethod
    def from_db(cls) -> Iterable['Song']:
        from mysql.connector import connect

        with connect(
            user="Estranger",
            password='********',
            host="127.0.0.1",
            port=3306,
            database="Music",
        ) as conn, conn.cursor() as cursor:
            cursor.execute(
                'select artist, album, title from songs '
                'order by artist, album'
            )
            for row in cursor.fetchall():
                yield cls(*row)

    @classmethod
    def fake_rows(cls) -> Iterable['Song']:
        return (
            cls('Weird Al Yankovic', 'Running with Scissors', 'Pretty Fly for a Rabbi'),
            cls('Weird Al Yankovic', 'Running with Scissors', 'Albuquerque'),
            cls('Weird Al Yankovic', 'Off the Deep End', "I Can't Watch This"),
            cls('Weird Al Yankovic', 'Off the Deep End', 'Smells like Nirvana'),
            cls('The Arrogant Worms', "C'est Cheese", 'Mounted Animal Nature Trail'),
        )

    def by_artist(self): return self.artist
    def by_album(self): return self.album


class GUI:
    HEADERS = ('Name', 'Artist', 'Album', 'Title')

    def __init__(self, songs):
        app = QApplication(sys.argv)

        tree = QTreeWidget()
        # a reference needs to be held or this will be garbage-collected
        self.tree = tree
        tree.resize(1280, 720)
        tree.setWindowTitle('tree')
        frame = tree.frameGeometry()
        center = tree.screen().availableGeometry().center()
        frame.moveCenter(center)
        tree.move(frame.topLeft())
        tree.setColumnCount(len(self.HEADERS))
        tree.setHeaderLabels(self.HEADERS)

        for artist, after_artist in groupby(songs, Song.by_artist):
            artist_node = QTreeWidgetItem((artist,))
            for album, after_album in groupby(after_artist, Song.by_album):
                album_node = QTreeWidgetItem((album, artist, album))
                for song in after_album:
                    song_node = QTreeWidgetItem((song.title, artist, album, song.title))
                    album_node.addChild(song_node)
                artist_node.addChild(album_node)
            tree.addTopLevelItem(artist_node)

        tree.show()

        self.run: Callable[[], int] = app.exec


def main() -> None:
    songs = Song.fake_rows()
    gui = GUI(songs)
    exit(gui.run())


if __name__ == '__main__':
    main()

тестовый забег

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *