Reputation: 25
This is a small example of my TableWidget:
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QIcon
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QStyledItemDelegate, QStyle, QTreeView, QApplication, \
QStyleOptionViewItem
from PyQt5.QtCore import Qt, QSize
leftArrow = "path\to\left-arrow.png"
downArrow = "path\to\down-arrow.png"
class GroupDelegate(QStyledItemDelegate):
def __init__(self, pic1=None, pic2=None, parent=None):
super().__init__(parent)
self._pic1 = QIcon(pic1)
self._pic2 = QIcon(pic2)
def initStyleOption(self, option, index):
option.state &= ~QStyle.State_Selected
super().initStyleOption(option, index)
if index.column() == index.model().columnCount() - 1 and not index.parent().isValid():
is_open = bool(option.state & QStyle.State_Open)
option.features |= QStyleOptionViewItem.HasDecoration
icon = self._pic2 if is_open else self._pic1
option.icon = icon
option.decorationSize = option.rect.size()
def sizeHint(self, option, index):
padding = 20
if index.data(Qt.UserRole + 1):
lines = index.data().count('\n') + 1
font_metrics = option.fontMetrics
line_height = font_metrics.lineSpacing()
return QSize(option.rect.width(), line_height * lines + padding)
return QSize(option.rect.width(), option.fontMetrics.height() + padding)
class GroupView(QTreeView):
def __init__(self, model, parent=None):
super().__init__(parent)
self.setIndentation(0)
self.clicked.connect(self.on_clicked)
self.setItemDelegate(GroupDelegate(pic1=leftArrow, pic2=downArrow, parent=self))
self.setModel(model)
def on_clicked(self, index):
if not index.parent().isValid():
parent_index = self.model().index(index.row(), 0)
self.setExpanded(parent_index, not self.isExpanded(parent_index))
class GroupModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
headers = ["Column 1", "Column 2", "Column 3", ""]
self.setColumnCount(len(headers))
for i, header in enumerate(headers):
self.setHeaderData(i, Qt.Horizontal, header)
def add_group(self, row_data, additional_info):
parent_items = [QStandardItem(col_data) for col_data in row_data]
for item in parent_items:
item.setEditable(False)
self.appendRow(parent_items)
additional_info_item = QStandardItem(additional_info)
additional_info_item.setData(True, Qt.UserRole + 1)
additional_info_item.setEditable(False)
parent_items[0].appendRow([additional_info_item] + [QStandardItem("")] * (self.columnCount() - 1))
return parent_items[0].index()
class TableWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
model = GroupModel(self)
tree_view = GroupView(model)
layout.addWidget(tree_view)
# Add sample data
model.add_group(["Parent Row 1", "Data 1", "Data 2"], "Additional info for Row 1")
model.add_group(["Parent Row 2", "Data 3", "Data 4"], "Additional info for Row 2")
# Span the additional info row across all columns
for row in range(model.rowCount()):
parent_index = model.index(row, 0)
additional_row = model.itemFromIndex(parent_index).rowCount() - 1
tree_view.setFirstColumnSpanned(additional_row, parent_index, True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = TableWidget()
window.show()
window.resize(600, 400)
sys.exit(app.exec_())
I want to indent the data in the expandable row. When I put self.setIndentation(0) to anything higher than 0 it is indenting but I also see the arrow on the left, which I don't want. As you can see in the code, I placed icons in the last column to place the arrows on the right side, so there's no need for the original arrow of TreeView.
My widget is based on this solution: How can I make a table that can collapse its rows into categories in Qt?
Additionally I wanna make the text I put in the expandable row with a darker background and round edges to look better.
I tried placing a label in it but this only resulted in the text not being visible at all anymore, maybe I did it the wrong way.
Hope there's a proper solution to my issue.
Summary: I wanna indent the text in the expandable row (without seeing the original arrow of QTreeView) and make the background light grey with round edges.
EDIT:
Considering musicamante's comments, this is my solution:
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QIcon, QColor, QPainterPath
from PyQt5.QtWidgets import QVBoxLayout, QWidget, QStyledItemDelegate, QStyle, QTreeView, QApplication, \
QStyleOptionViewItem
from PyQt5.QtCore import Qt, QSize, QRectF
leftArrow = "path\to\left-arrow.png"
downArrow = "path\to\down-arrow.png"
class GroupDelegate(QStyledItemDelegate):
def __init__(self, pic1=None, pic2=None, parent=None):
super().__init__(parent)
self._pic1 = QIcon(pic1)
self._pic2 = QIcon(pic2)
def initStyleOption(self, option, index):
option.state &= ~QStyle.State_Selected
super().initStyleOption(option, index)
if index.column() == index.model().columnCount() - 1 and not index.parent().isValid():
is_open = bool(option.state & QStyle.State_Open)
option.features |= QStyleOptionViewItem.HasDecoration
icon = self._pic2 if is_open else self._pic1
option.icon = icon
option.decorationSize = option.rect.size()
def sizeHint(self, option, index):
padding = 20
if index.data(Qt.UserRole + 1):
lines = index.data().count('\n') + 1
font_metrics = option.fontMetrics
line_height = font_metrics.lineSpacing()
return QSize(option.rect.width(), line_height * lines + padding)
return QSize(option.rect.width(), option.fontMetrics.height() + padding)
def paint(self, painter, option, index):
if index.data(Qt.UserRole + 1):
painter.save()
lightGrey = QColor(220, 220, 220)
painter.setBrush(lightGrey)
painter.setPen(Qt.NoPen)
rect = option.rect
rect.setLeft(option.rect.left())
rect.setRight(option.rect.right())
rect_f = QRectF(rect)
radius = 10
path = QPainterPath()
path.addRoundedRect(rect_f, radius, radius)
painter.fillPath(path, lightGrey)
painter.restore()
rect = option.rect
rect.setLeft(option.rect.left())
else:
super().paint(painter, option, index)
super().paint(painter, option, index)
class GroupView(QTreeView):
def __init__(self, model, parent=None):
super().__init__(parent)
self.setIndentation(20)
self.setRootIsDecorated(False)
self.clicked.connect(self.on_clicked)
self.setItemDelegate(GroupDelegate(pic1=leftArrow, pic2=downArrow, parent=self))
self.setModel(model)
def on_clicked(self, index):
if not index.parent().isValid():
parent_index = self.model().index(index.row(), 0)
self.setExpanded(parent_index, not self.isExpanded(parent_index))
class GroupModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
headers = ["Column 1", "Column 2", "Column 3", ""]
self.setColumnCount(len(headers))
for i, header in enumerate(headers):
self.setHeaderData(i, Qt.Horizontal, header)
def add_group(self, row_data, additional_info):
parent_items = [QStandardItem(col_data) for col_data in row_data]
for item in parent_items:
item.setEditable(False)
self.appendRow(parent_items)
additional_info_item = QStandardItem(additional_info)
additional_info_item.setData(True, Qt.UserRole + 1)
additional_info_item.setEditable(False)
parent_items[0].appendRow([additional_info_item])
return parent_items[0].index()
class TableWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
model = GroupModel(self)
tree_view = GroupView(model)
layout.addWidget(tree_view)
model.add_group(["Parent Row 1", "Data 1", "Data 2"], "Additional info for Row 1")
model.add_group(["Parent Row 2", "Data 3", "Data 4"], "Additional info for Row 2")
for row in range(model.rowCount()):
parent_index = model.index(row, 0)
additional_row = model.itemFromIndex(parent_index).rowCount() - 1
tree_view.setFirstColumnSpanned(additional_row, parent_index, True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = TableWidget()
window.show()
window.resize(600, 400)
sys.exit(app.exec_())
there's also another approach to indent the data in expandable row with paint event (setting self.setIndentation to 0) but this was the easier and shorter solution
Upvotes: 0
Views: 34