PyQt5 QTableView选择单元格背景与Delegate

PyQt5 QTableView selected cell background with Delegate

我有一个使用 QTableView/QAbstractTableModel 组合的应用程序。对于视图,我定义了一个委托,它在 table 视图的一列中显示图像(QPixmap,从图像文件加载)。

Basically, the problem is that when a cell in the column with the Delegate is selected, sometimes the background shows and sometimes it doesn't.

这是我到目前为止通过实验发现的,但我不太明白:

我有这个比较短的测试程序:

from PyQt5 import QtCore, QtWidgets, QtGui
import sys


# ------------------------------------------------------------------------------
class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data = [[]], headers = None, parent = None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.__data = data

    def rowCount(self, parent):
        return len(self.__data)

    def columnCount(self, parent):
        return len(self.__data[0])

    def data(self, index, role):
        row = index.row()
        column = index.column()
        if role == QtCore.Qt.DisplayRole:
            value = self.__data[row][column]
            return value

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsSelectable


# ------------------------------------------------------------------------------
class Delegate(QtWidgets.QStyledItemDelegate):

    def paint(self, painter, option, index):        

        if (index.column() == 0):
            image = QtGui.QImage('open.png')
            pixmap = QtGui.QPixmap.fromImage(image)

            x = option.rect.center().x() - pixmap.rect().width() / 2
            y = option.rect.center().y() - pixmap.rect().height() / 2
            painter.drawPixmap(x, y, pixmap)


# ------------------------------------------------------------------------------
if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('fusion')

    tableView = QtWidgets.QTableView()
    tableView.setItemDelegateForColumn(0, Delegate())
    tableView.resize(550, 160)
    tableView.show()

    rowCount = 3
    columnCount = 4
    data = [
        [i for i in range(columnCount)]
        for j in range(rowCount)
    ]

    model = TableModel(data)
    tableView.setModel(model)

    sys.exit(app.exec_())

When I specify app.setStyle('fusion') in __main__, I get what I would expect: When a cell in the column with the Delegate is selected, the cell background is blue and the image appears in front其中:

但是,如果我更改为 app.setStyle('windows'),即使通常它对所选单元格使用相同的蓝色背景,当我移动到第一列中的单元格时,背景消失了:

(你看不出来,但是选中了和第一个例子一样的单元格)

那只是一段测试代码,我没有完全看懂。

在我编写的实际应用程序中,我使用 Qt Designer 创建 UI。尽管我指定了 app.setStyle('fusion'),但 table 具有完全不同的样式,与选定单元格的背景具有不同的外观:

我这辈子都弄不明白它从哪里获得了不同的风格。它必须以某种方式来自 Qt Designer,但我查看了 Qt Designer 创建的 .py 文件,但找不到它。

这种风格(无论它来自哪里)似乎都遇到了与 windows 风格相同的问题。在上图中,没有使用任何委托。选中第2行/第2列的单元格,背景显示。

但是如果我在第 2 列中添加一个 Delegate 来显示一个 QPixmap,那么当单元格被选中时背景不会显示:

(已选中;请相信我)。

我想可能是这样的情况,一旦您使用 Delegate 显示图像,您就无法再在所选单元格中获得背景。但你显然可以。它在一种情况下有效,但在其他情况下无效。

如果有人能阐明这一点,我将不胜感激。 (我知道这很长;感谢您坚持我)。

我一直在摆弄这个问题,并且我对我的原始问题有所了解。回想起来,我认为它并不像它本来应该的那样清晰(或者也许我只是更好地理解了它)。

首先,我不应该将细胞称为 "selected"。事实上,我什至没有为视图中的任何单元格设置 Qt.ItemIsSelectable 标志。我真正想做的是在 active 时控制单元格的背景(因为缺少更好的词)——意思是它是光标当前所在的单元格.

这可以通过覆盖委托中的 initStyleOption() 来完成。我原来的测试代码修改如下图:

from PyQt5 import QtCore, QtWidgets, QtGui
import sys


# ------------------------------------------------------------------------------
class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, data = [[]], headers = None, parent = None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.__data = data

    def rowCount(self, parent):
        return len(self.__data)

    def columnCount(self, parent):
        return len(self.__data[0])

    def data(self, index, role):
        row = index.row()
        column = index.column()
        if role == QtCore.Qt.DisplayRole:
            value = self.__data[row][column]
            return value
        if role == QtCore.Qt.BackgroundRole:
            return QtGui.QBrush(QtGui.QColor(255, 255, 255))

    def flags(self, index):
        return QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsEditable


# ------------------------------------------------------------------------------
class TableView(QtWidgets.QTableView):

    def __init__(self, parent=None):
        super().__init__(parent)


# ------------------------------------------------------------------------------
class Delegate(QtWidgets.QStyledItemDelegate):

    # <Modification>
    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        if (
            index.row() == tableView.currentIndex().row() and
            index.column() == tableView.currentIndex().column()
        ):
            option.backgroundBrush = QtGui.QBrush(QtGui.QColor(232, 244, 252))

    def paint(self, painter, option, index):        
        if (index.column() == 0):

            # <Modification>
            if (
                index.row() == tableView.currentIndex().row() and
                index.column() == tableView.currentIndex().column()
            ):
                self.initStyleOption(option, index)
                painter.setPen(QtCore.Qt.NoPen)
                painter.setBrush(option.backgroundBrush)
                painter.drawRect(option.rect)

            image = QtGui.QImage('open.png')
            pixmap = QtGui.QPixmap.fromImage(image)
            x = option.rect.center().x() - pixmap.rect().width() / 2
            y = option.rect.center().y() - pixmap.rect().height() / 2
            painter.drawPixmap(x, y, pixmap)

        else:
            super().paint(painter, option, index)



# ------------------------------------------------------------------------------
if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    app.setStyle('fusion')

    tableView = TableView()
    tableView.resize(550, 160)
    tableView.setItemDelegate(Delegate())
    tableView.show()

    rowCount = 3
    columnCount = 4
    data = [
        [i for i in range(columnCount)]
        for j in range(rowCount)
    ]

    model = TableModel(data)
    tableView.setModel(model)

    sys.exit(app.exec_())

initStyleOption() 设置单元格处于活动状态(当前)时的背景画笔。但正如我之前抱怨的那样,这不会出现在第一列中,它有一个带有显示像素图的自定义 paint() 方法的委托。因此 paint() 还必须负责为该列中处于活动状态的单元格设置背景。它使用与 initStyleOption() 设置相同的 backgroundBrush

结果非常接近我的目标。唯一美中不足的是,显然还有其他样式正在进行,这会影响视图中的所有单元格,但第 1 列中带有自定义委托的单元格除外。所以它们看起来相当在活动时完全一样:

(这很微妙,但是第 2 列中的单元格背景有一点渐变,而第 1 列中没有)。

我现在知道有一些样式 'factories' 应用了小部件范围的样式。由于我使用的是 Fusion,这显然是额外样式的来源。

所以现在我的问题是——样式是在哪里定义的,我可以控制它吗?如果我能看到它,我可以让我的自定义背景样式与它相匹配。更好的是,如果我可以修改它,我可以使它与我的匹配。

今天我自己的工具也遇到了同样的问题。我认为您的问题与 this other question 相同。简而言之,您只需在 paint 中调用 super 即可,然后再进行任何额外工作。当我将 super 添加到我自己的代码时,选择在委托中再次按预期工作。

class Delegate(QtWidgets.QStyledItemDelegate):

    def paint(self, painter, option, index):        
        super().paint(painter, option, index)

        if (index.column() == 0):
            image = QtGui.QImage('open.png')
            pixmap = QtGui.QPixmap.fromImage(image)

            x = option.rect.center().x() - pixmap.rect().width() / 2
            y = option.rect.center().y() - pixmap.rect().height() / 2
            painter.drawPixmap(x, y, pixmap)

(FWIW 我还没有测试上面的代码。但它应该工作)。