by Anonymous » 06 Jan 2025, 06:02
Ich möchte eine Liste von Dateien mit Sternebewertung in einer QTableView anzeigen. Dazu verwende ich den folgenden Delegaten:
Code: Select all
class StarRatingDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter, option, index):
file: File = index.data(Qt.UserRole)
star_rating_widget = StarRatingWidget(10, self.parent())
star_rating_widget.set_rating(file.rating)
star_rating_widget.render(painter, option.rect.topLeft())
Ist ein einfaches QWidget, das 5 QLables in einem QHBoxLayout enthält.
Das funktioniert bisher alles, aber alle StarRatingWidgets werden nach links oben verschoben:
In der ersten Spalte wird die Bewertung als Zahl angezeigt. Sie können sehen, dass alle Sterne leicht nach links und etwas mehr als eine Zeilenhöhe nach oben verschoben sind.
Tests haben ergeben, dass option.rect die Koordinaten mit zurückgibt (0, 0) ist die obere linke Ecke der ersten Zelle, aber star_rating_widget.render behandelt die Koordinaten so, dass (0, 0) die obere linke Ecke des Fensters ist. Die Widgets werden also um den Abstand zwischen Tabelle und Fensterrand und zusätzlich um die Höhe des Tabellenkopfes verschoben.
Bevor jemand fragt, hier der vollständige Code. Zum Ausführen ist pyside6 erforderlich.
Code: Select all
#!/usr/bon/env python
from PySide6.QtCore import Qt, Signal, QAbstractItemModel, QModelIndex, QEvent
from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import QApplication, QLabel, QTableView, QMainWindow, QSizePolicy, QHBoxLayout, QWidget, QStyledItemDelegate
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 250, 600)
self.central_widget = QWidget()
self.main_layout = QHBoxLayout()
self.central_widget.setLayout(self.main_layout)
self.setCentralWidget(self.central_widget)
self.list = QTableView()
self.list.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
self.list.setSelectionMode(QTableView.SelectionMode.SingleSelection)
self.list.horizontalHeader().setStretchLastSection = True
self.list.verticalHeader().hide()
self.list.show_grid = False
self.list.setItemDelegateForColumn(1, StarRatingDelegate(self.list))
self.list.setModel(ListModel())
self.main_layout.addWidget(self.list)
class ListModel(QAbstractItemModel):
def __init__(self):
super().__init__()
self.horizontal_header_labels = ['Number', 'Stars']
def rowCount(self, parent=QModelIndex()):
return 50
def columnCount(self, parent=QModelIndex()):
return len(self.horizontal_header_labels)
def data(self, index, role):
if not index.isValid():
return None
if role == Qt.DisplayRole:
rating = (index.row() - 2) % 7
return None if rating >= 5 else rating + 1
return None
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.horizontal_header_labels[section]
return None
def index(self, row, column, parent=QModelIndex()):
if self.hasIndex(row, column, parent):
return self.createIndex(row, column)
return QModelIndex()
def parent(self, index):
return QModelIndex()
class StarRatingWidget(QWidget):
rating_changed = Signal(int)
def __init__(self, font_size, parent=None):
super().__init__(parent)
self.rating = 0
self.hovered_star: int|None = None
self.stars: List[QLabel] = []
self.font_size: int = font_size
self.init_ui()
def star_mouse_event(self, i: int):
def event(event: QMouseEvent):
if event.type() == QEvent.Enter:
self.hovered_star = i
self.update()
elif event.type() == QEvent.Leave:
self.hovered_star = None
self.update()
return event
def init_ui(self):
layout = QHBoxLayout()
for i in range(5):
star = QLabel()
star.mousePressEvent = lambda _, i=i: self.set_rating(i + 1)
star.enterEvent = self.star_mouse_event(i)
star.leaveEvent = self.star_mouse_event(i)
star.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
layout.addWidget(star)
self.stars.append(star)
self.setLayout(layout)
self.update()
def set_rating(self, rating: int|None):
if rating != self.rating:
self.rating = rating
self.update()
self.rating_changed.emit(rating)
def update(self):
for i, star in enumerate(self.stars):
rating = self.rating if self.rating is not None else 0
if i < rating:
star.setText('★')
else:
star.setText('☆')
if self.rating is None:
color = 'gray'
weight = 'normal'
elif i == self.hovered_star:
color = 'blue'
weight = 'bold'
else:
color = 'yellow'
weight = 'normal'
star.setStyleSheet(f'font-size: {self.font_size}px; color: {color}; font-weight: {weight}')
class StarRatingDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter, option, index):
rating = index.data()
star_rating_widget = StarRatingWidget(10, self.parent())
star_rating_widget.set_rating(rating)
star_rating_widget.render(painter, option.rect.topLeft())
def main():
app = QApplication([])
main_window = MainWindow()
main_window.show()
QApplication.exec()
if __name__ == '__main__':
main()
Ich möchte eine Liste von Dateien mit Sternebewertung in einer QTableView anzeigen. Dazu verwende ich den folgenden Delegaten:
[code]class StarRatingDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter, option, index):
file: File = index.data(Qt.UserRole)
star_rating_widget = StarRatingWidget(10, self.parent())
star_rating_widget.set_rating(file.rating)
star_rating_widget.render(painter, option.rect.topLeft())
[/code]
[code]StarRatingWidget[/code] Ist ein einfaches QWidget, das 5 QLables in einem QHBoxLayout enthält.
Das funktioniert bisher alles, aber alle StarRatingWidgets werden nach links oben verschoben:
[img]https://i.sstatic.net/F2zRCMVo.png[/img]
In der ersten Spalte wird die Bewertung als Zahl angezeigt. Sie können sehen, dass alle Sterne leicht nach links und etwas mehr als eine Zeilenhöhe nach oben verschoben sind.
Tests haben ergeben, dass option.rect die Koordinaten mit zurückgibt (0, 0) ist die obere linke Ecke der ersten Zelle, aber star_rating_widget.render behandelt die Koordinaten so, dass (0, 0) die obere linke Ecke des Fensters ist. Die Widgets werden also um den Abstand zwischen Tabelle und Fensterrand und zusätzlich um die Höhe des Tabellenkopfes verschoben.
Bevor jemand fragt, hier der vollständige Code. Zum Ausführen ist pyside6 erforderlich.
[code]#!/usr/bon/env python
from PySide6.QtCore import Qt, Signal, QAbstractItemModel, QModelIndex, QEvent
from PySide6.QtGui import QMouseEvent
from PySide6.QtWidgets import QApplication, QLabel, QTableView, QMainWindow, QSizePolicy, QHBoxLayout, QWidget, QStyledItemDelegate
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setGeometry(100, 100, 250, 600)
self.central_widget = QWidget()
self.main_layout = QHBoxLayout()
self.central_widget.setLayout(self.main_layout)
self.setCentralWidget(self.central_widget)
self.list = QTableView()
self.list.setSelectionBehavior(QTableView.SelectionBehavior.SelectRows)
self.list.setSelectionMode(QTableView.SelectionMode.SingleSelection)
self.list.horizontalHeader().setStretchLastSection = True
self.list.verticalHeader().hide()
self.list.show_grid = False
self.list.setItemDelegateForColumn(1, StarRatingDelegate(self.list))
self.list.setModel(ListModel())
self.main_layout.addWidget(self.list)
class ListModel(QAbstractItemModel):
def __init__(self):
super().__init__()
self.horizontal_header_labels = ['Number', 'Stars']
def rowCount(self, parent=QModelIndex()):
return 50
def columnCount(self, parent=QModelIndex()):
return len(self.horizontal_header_labels)
def data(self, index, role):
if not index.isValid():
return None
if role == Qt.DisplayRole:
rating = (index.row() - 2) % 7
return None if rating >= 5 else rating + 1
return None
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self.horizontal_header_labels[section]
return None
def index(self, row, column, parent=QModelIndex()):
if self.hasIndex(row, column, parent):
return self.createIndex(row, column)
return QModelIndex()
def parent(self, index):
return QModelIndex()
class StarRatingWidget(QWidget):
rating_changed = Signal(int)
def __init__(self, font_size, parent=None):
super().__init__(parent)
self.rating = 0
self.hovered_star: int|None = None
self.stars: List[QLabel] = []
self.font_size: int = font_size
self.init_ui()
def star_mouse_event(self, i: int):
def event(event: QMouseEvent):
if event.type() == QEvent.Enter:
self.hovered_star = i
self.update()
elif event.type() == QEvent.Leave:
self.hovered_star = None
self.update()
return event
def init_ui(self):
layout = QHBoxLayout()
for i in range(5):
star = QLabel()
star.mousePressEvent = lambda _, i=i: self.set_rating(i + 1)
star.enterEvent = self.star_mouse_event(i)
star.leaveEvent = self.star_mouse_event(i)
star.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
layout.addWidget(star)
self.stars.append(star)
self.setLayout(layout)
self.update()
def set_rating(self, rating: int|None):
if rating != self.rating:
self.rating = rating
self.update()
self.rating_changed.emit(rating)
def update(self):
for i, star in enumerate(self.stars):
rating = self.rating if self.rating is not None else 0
if i < rating:
star.setText('★')
else:
star.setText('☆')
if self.rating is None:
color = 'gray'
weight = 'normal'
elif i == self.hovered_star:
color = 'blue'
weight = 'bold'
else:
color = 'yellow'
weight = 'normal'
star.setStyleSheet(f'font-size: {self.font_size}px; color: {color}; font-weight: {weight}')
class StarRatingDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter, option, index):
rating = index.data()
star_rating_widget = StarRatingWidget(10, self.parent())
star_rating_widget.set_rating(rating)
star_rating_widget.render(painter, option.rect.topLeft())
def main():
app = QApplication([])
main_window = MainWindow()
main_window.show()
QApplication.exec()
if __name__ == '__main__':
main()
[/code]