Click here to Skip to main content
15,881,089 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
I am using Python 3.9.5

I want to create a PyQt6 music player, I intend its main interface to be a tree-like view, it should have multiple columns, and should have three levels, first level are the artists, second level are the albums and third level are the songs. with plus sign for expanding button and minus sign for collapsing button, with checkboxs and branch lines, and it should be editable, it should know which items are selected, and know where the cursor is, and text should scroll from right to left if the text is too long for the cells.

And the color scheme should be dark mode.

I want to ask about the tree-like view part, I never made a GUI application before but I am very familiar with Python, and I have Google searched for dozens of days for online tutorials and couldn't find anything useful, Google really is very not useful.

Here is a working demo, minus the editable text part, checkbox part and scrolling text part, and interactive part, and styling part, but the tree-structure hierarchy is done right:

Python
import mysql.connector
import sys
from collections import defaultdict
from collections import namedtuple
from operator import attrgetter
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication, QLabel, QTreeWidget, QTreeWidgetItem

def fetchdata():
    proper = lambda x: x[0].upper() + x[1:]
    song = namedtuple('song', 'artist title album')
    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')
    return sorted([song(*map(proper, i)) for i in cursor.fetchall()], key=attrgetter('artist', 'album'))

def treefy(data):
    entries = defaultdict(lambda: defaultdict(list))
    for artist, title, album in data:
        entries[artist][album].append(title)
    return entries

songs = fetchdata()
entries = treefy(songs)

app = QApplication(sys.argv)
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'])
tree.setFont(QFont('Noto Mono', 10, 2))
for i in range(4): tree.setColumnWidth(i, 300)
tree.setAutoScroll(True)
tree.setIndentation(32)
tree.setAlternatingRowColors(True)

for artist, albums in entries.items():
    artist_node = QTreeWidgetItem([artist])
    for album, songs in albums.items():
        album_node = QTreeWidgetItem([album, artist, album])
        for song in songs:
            song_node = QTreeWidgetItem([song, artist, album, song])
            album_node.addChild(song_node)
        artist_node.addChild(album_node)
    tree.addTopLevelItem(artist_node)

tree.show()
sys.exit(app.exec())


And I just couldn't find a way to add the checkboxs, QLineEdits, scrolling texts, branching lines and interactive part to the script, and I need it to be refreshable, the treeview should update after certain actions are performed.

And I have a color palette generated using Qt Designer, though I don't know how to use it:

from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(1280, 720)
        MainWindow.setBaseSize(QSize(1280, 720))
        palette = QPalette()
        brush = QBrush(QColor(178, 255, 255, 255))
        brush.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.WindowText, brush)
        brush1 = QBrush(QColor(1, 35, 69, 255))
        brush1.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Button, brush1)
        brush2 = QBrush(QColor(0, 255, 255, 255))
        brush2.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Light, brush2)
        brush3 = QBrush(QColor(0, 128, 255, 255))
        brush3.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Midlight, brush3)
        brush4 = QBrush(QColor(0, 0, 64, 255))
        brush4.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Dark, brush4)
        brush5 = QBrush(QColor(0, 0, 96, 255))
        brush5.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Mid, brush5)
        brush6 = QBrush(QColor(115, 176, 200, 255))
        brush6.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Text, brush6)
        brush7 = QBrush(QColor(0, 192, 255, 255))
        brush7.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.BrightText, brush7)
        brush8 = QBrush(QColor(64, 0, 255, 255))
        brush8.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.ButtonText, brush8)
        brush9 = QBrush(QColor(1, 18, 60, 255))
        brush9.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Base, brush9)
        brush10 = QBrush(QColor(25, 40, 60, 255))
        brush10.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Window, brush10)
        brush11 = QBrush(QColor(0, 0, 128, 255))
        brush11.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Shadow, brush11)
        brush12 = QBrush(QColor(0, 120, 215, 255))
        brush12.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.Highlight, brush12)
        brush13 = QBrush(QColor(192, 255, 255, 255))
        brush13.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.HighlightedText, brush13)
        palette.setBrush(QPalette.Active, QPalette.Link, brush2)
        brush14 = QBrush(QColor(255, 0, 255, 255))
        brush14.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.LinkVisited, brush14)
        brush15 = QBrush(QColor(2, 72, 172, 255))
        brush15.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.AlternateBase, brush15)
        brush16 = QBrush(QColor(0, 0, 0, 255))
        brush16.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.NoRole, brush16)
        brush17 = QBrush(QColor(2, 36, 120, 255))
        brush17.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.ToolTipBase, brush17)
        brush18 = QBrush(QColor(80, 40, 192, 255))
        brush18.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.ToolTipText, brush18)
        brush19 = QBrush(QColor(128, 128, 128, 255))
        brush19.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Active, QPalette.PlaceholderText, brush19)
        palette.setBrush(QPalette.Inactive, QPalette.WindowText, brush16)
        brush20 = QBrush(QColor(240, 240, 240, 255))
        brush20.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Button, brush20)
        brush21 = QBrush(QColor(255, 255, 255, 255))
        brush21.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Light, brush21)
        brush22 = QBrush(QColor(227, 227, 227, 255))
        brush22.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Midlight, brush22)
        brush23 = QBrush(QColor(160, 160, 160, 255))
        brush23.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Dark, brush23)
        palette.setBrush(QPalette.Inactive, QPalette.Mid, brush23)
        palette.setBrush(QPalette.Inactive, QPalette.Text, brush16)
        palette.setBrush(QPalette.Inactive, QPalette.BrightText, brush21)
        palette.setBrush(QPalette.Inactive, QPalette.ButtonText, brush16)
        palette.setBrush(QPalette.Inactive, QPalette.Base, brush21)
        palette.setBrush(QPalette.Inactive, QPalette.Window, brush20)
        brush24 = QBrush(QColor(105, 105, 105, 255))
        brush24.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Shadow, brush24)
        palette.setBrush(QPalette.Inactive, QPalette.Highlight, brush20)
        palette.setBrush(QPalette.Inactive, QPalette.HighlightedText, brush16)
        brush25 = QBrush(QColor(0, 0, 255, 255))
        brush25.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.Link, brush25)
        palette.setBrush(QPalette.Inactive, QPalette.LinkVisited, brush14)
        brush26 = QBrush(QColor(245, 245, 245, 255))
        brush26.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.AlternateBase, brush26)
        brush27 = QBrush(QColor(0, 0, 0, 255))
        brush27.setStyle(Qt.NoBrush)
        palette.setBrush(QPalette.Inactive, QPalette.NoRole, brush27)
        brush28 = QBrush(QColor(255, 255, 220, 255))
        brush28.setStyle(Qt.SolidPattern)
        palette.setBrush(QPalette.Inactive, QPalette.ToolTipBase, brush28)
        palette.setBrush(QPalette.Inactive, QPalette.ToolTipText, brush16)
        brush29 = QBrush(QColor(0, 0, 0, 255))
        brush29.setStyle(Qt.NoBrush)
        palette.setBrush(QPalette.Inactive, QPalette.PlaceholderText, brush29)
        palette.setBrush(QPalette.Disabled, QPalette.WindowText, brush4)
        palette.setBrush(QPalette.Disabled, QPalette.Button, brush1)
        palette.setBrush(QPalette.Disabled, QPalette.Light, brush2)
        palette.setBrush(QPalette.Disabled, QPalette.Midlight, brush3)
        palette.setBrush(QPalette.Disabled, QPalette.Dark, brush4)
        palette.setBrush(QPalette.Disabled, QPalette.Mid, brush5)
        palette.setBrush(QPalette.Disabled, QPalette.Text, brush4)
        palette.setBrush(QPalette.Disabled, QPalette.BrightText, brush7)
        palette.setBrush(QPalette.Disabled, QPalette.ButtonText, brush4)
        palette.setBrush(QPalette.Disabled, QPalette.Base, brush10)
        palette.setBrush(QPalette.Disabled, QPalette.Window, brush10)
        palette.setBrush(QPalette.Disabled, QPalette.Shadow, brush16)
        palette.setBrush(QPalette.Disabled, QPalette.Highlight, brush12)
        palette.setBrush(QPalette.Disabled, QPalette.HighlightedText, brush21)
        palette.setBrush(QPalette.Disabled, QPalette.Link, brush25)
        palette.setBrush(QPalette.Disabled, QPalette.LinkVisited, brush14)
        palette.setBrush(QPalette.Disabled, QPalette.AlternateBase, brush26)
        brush30 = QBrush(QColor(0, 0, 0, 255))
        brush30.setStyle(Qt.NoBrush)
        palette.setBrush(QPalette.Disabled, QPalette.NoRole, brush30)
        palette.setBrush(QPalette.Disabled, QPalette.ToolTipBase, brush28)
        palette.setBrush(QPalette.Disabled, QPalette.ToolTipText, brush16)
        brush31 = QBrush(QColor(0, 0, 0, 255))
        brush31.setStyle(Qt.NoBrush)
        palette.setBrush(QPalette.Disabled, QPalette.PlaceholderText, brush31)
        MainWindow.setPalette(palette)
        font = QFont()
        font.setFamilies([u"Noto Serif"])
        font.setPointSize(10)
        MainWindow.setFont(font)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(MainWindow)
        self.menubar.setObjectName(u"menubar")
        self.menubar.setGeometry(QRect(0, 0, 1280, 24))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QStatusBar(MainWindow)
        self.statusbar.setObjectName(u"statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.retranslateUi(MainWindow)
        QMetaObject.connectSlotsByName(MainWindow)


What I have tried:

Google really sucks, everything I have found, the tutorials are too simple and irrelevant to what I want to do, the official documentation of Qt is about C++, and everything I have found on stackoverflow are outdated and don't cover all the aspects.

But I have found that I can add checkboxs using
Qt.ItemFlag.ItemIsUserCheckable


The outdated things I have found are all using Qt.ItemIsUserCheckable which won't work anymore, similarly, I can make a node tristate by
Qt.ItemFlag.ItemIsAutoTristate
, whereas the outdated sources only use
Qt.ItemIsTristate
, and I can use
Qt.CheckState.Unchecked
to make items unchecked.

This is a script I have found that creates a QtreeWidget with checkboxs, I updated it to PyQt6:

from PyQt6.QtCore import * 
from PyQt6.QtGui import * 
from PyQt6.QtWidgets import *
import sys

def main(): 
    app     = QApplication (sys.argv)
    tree    = QTreeWidget ()
    headerItem  = QTreeWidgetItem()
    item    = QTreeWidgetItem()
    for i in range(3):
        parent = QTreeWidgetItem(tree)
        parent.setText(0, "Parent {}".format(i))
        parent.setFlags(parent.flags() | Qt.ItemFlag.ItemIsUserTristate | Qt.ItemFlag.ItemIsUserCheckable)
        for x in range(5):
            child = QTreeWidgetItem(parent)
            child.setFlags(child.flags() | Qt.ItemFlag.ItemIsUserCheckable)
            child.setText(0, "Child {}".format(x))
            child.setCheckState(0, Qt.CheckState.Unchecked)
    tree.show() 
    sys.exit(app.exec())

if __name__ == '__main__':
    main()


But it doesn't have branchlines and multiple columns and isn't editable and so on.

And I have found I can make an item selectable and editable by setting:

Qt.ItemFlag.ItemIsSelectable
and
Qt.ItemFlag.ItemIsEditable
.

I have found a way to make the text scroll:
class scrolling_text:
    def __init__(self, text):
        self.text = text
        self.original_text = text
    
    def scroll(self):
        self.text = self.text[1:] + self.text[:1]
    
    def get_text(self):
        if len(self.text) > 32:
            self.scroll()
        return self.text


Now if you create an object of the above class, with a string of more than 32 characters, then everytime get_text if called, the text will be shifted by one character leftwards, while calling original_text will always return the unchanged text.

Then I can use a QTimer to periodically setText.

But I just can't make it work:

import mysql.connector
import sys
from collections import defaultdict
from collections import namedtuple
from operator import attrgetter
from PyQt6.QtCore import QTimer, Qt
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication, QLabel, QTreeWidget, QTreeWidgetItem

def fetchdata():
    proper = lambda x: x[0].upper() + x[1:]
    song = namedtuple('song', 'artist title album')
    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')
    return sorted([song(*map(proper, i)) for i in cursor.fetchall()], key=attrgetter('artist', 'album'))

def treefy(data):
    entries = defaultdict(lambda: defaultdict(list))
    for artist, title, album in data:
        entries[artist][album].append(title)
    return entries

songs = fetchdata()
entries = treefy(songs)



app = QApplication(sys.argv)
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'])
tree.setFont(QFont('Noto Mono', 10, 2))
for i in range(4): tree.setColumnWidth(i, 300)
tree.setAutoScroll(True)
tree.setIndentation(32)
tree.setAlternatingRowColors(True)


class scrolling_text:
    def __init__(self, text):
        self.text = text
        self.original_text = text
    
    def scroll(self):
        self.text = self.text[1:] + self.text[:1]
    
    def get_text(self):
        if len(self.text) > 32:
            self.scroll()
        return self.text

class treenode(QTreeWidgetItem):
    def __init__(self, parent, texts):
        super().__init__(parent)
        self.artist = texts[1]
        self.name = texts[0]
        if len(texts) == 3:
            self.album = texts[2]
        elif len(texts) == 4:
            self.title = texts[3]
        self.timer = QTimer()
        self.timer.setInterval(250)
        self.timer.timeout.connect(self.init(texts))
    def init(self, texts):
        texts = [scrolling_text(i) for i in texts]
        self.setText(0, texts[0].get_text())
        for i, text in enumerate(texts[1:]):
            self.setText(i + 1, text.get_text())

root = QTreeWidgetItem()

for artist, albums in entries.items():
    artist_node = treenode(root, [artist, artist])
    for album, songs in albums.items():
        album_node = treenode(artist_node, [album, artist, album])
        for song in songs:
            song_node = QTreeWidgetItem(album_node, [song, artist, album, song])

tree.addTopLevelItem(root)

tree.show()
sys.exit(app.exec())


Trying to execute it raises
TypeError: argument 1 has unexpected type 'NoneType'
.

How can I fix the code?

And How can I make the treeview meet all the criteria above?
Posted
Updated 29-Jun-21 0:28am

1 solution

Quote:
How can I fix the code? And How can I make the treeview meet all the criteria above?

Just posting a lot of code with such general questions makes it difficult to know where to start. And given that you have stated that you are not familiar with GUI and thus QT, I recommend you start with Riverbank Computing | Introduction[^] and Qt | Cross-platform software development for embedded & desktop[^].
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900