PyQt5 QTableView 用 Delegate 选择单元格背景
Posted
技术标签:
【中文标题】PyQt5 QTableView 用 Delegate 选择单元格背景【英文标题】:PyQt5 QTableView selected cell background with Delegate 【发布时间】:2019-02-02 05:16:53 【问题描述】:我有一个使用 QTableView/QAbstractTableModel 组合的应用程序。对于视图,我定义了一个 Delegate,它在表格视图的一列中显示图像(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 of it:
但是,如果我更改为app.setStyle('windows')
,即使通常它对选定的单元格使用相同的蓝色背景,当我移动到第一列中的单元格时,背景也会消失:
(您显然看不到它,但选择了与第一个示例相同的单元格)。
那只是一段测试代码,我没完全看懂。
在我编写的实际应用程序中,我使用 Qt Designer 创建 UI。即使我指定了app.setStyle('fusion')
,表格的样式也完全不同,与所选单元格的背景外观不同:
我终其一生都无法弄清楚它在哪里拾取了不同的风格。它必须以某种方式来自 Qt Designer,但我查看了 Qt Designer 创建的 .py 文件,但找不到。
这种风格(无论它来自哪里)似乎与windows
风格有同样的问题。在上图中,没有使用委托。选中第 2 行/第 2 列的单元格,并显示背景。
但如果我在第2列中添加委托委托委员会以显示QPIXMAP,则在选择单元格时未显示背景:
(已选中;相信我的话)。
我认为可能是这样的情况,一旦您使用委托来显示图像,您将无法再在所选单元格中获得背景。但你显然可以。它在一种情况下有效,在其他情况下无效。
如果有人能阐明这一点,我将不胜感激。 (我知道这很长;感谢您的支持)。
【问题讨论】:
更新:不要相信我的话。实际上,显示的单元格没有被选中。事实上,我什至没有为视图设置 Qt.ItemIsSelectable 标志。上图第 2 行/第 2 列(带有“V”的单元格)中显示的淡蓝色突出显示不是因为选择了该单元格,而是因为它是当前单元格(即光标有没有)。但我仍然不知道该样式的来源...... 【参考方案1】:我一直在摆弄这个问题,并且对我最初的问题有所了解。回想起来,我认为它并没有想象的那么清楚(或者我只是理解得更好一点)。
首先,我不应该将单元格称为“选中”。事实上,我什至没有为视图中的任何单元格设置Qt.ItemIsSelectable
标志。我真正一直在尝试做的是控制单元格的背景,当它 active 时(因为没有更好的词)——这意味着它是光标当前所在的单元格。 p>
这可以通过覆盖委托中的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 列中没有)。
我现在知道有些样式“工厂”应用了小部件范围的样式。由于我使用的是 Fusion,这显然是额外样式的来源。
所以现在我的问题是——该样式在哪里定义,我可以控制它吗?如果我能看到它,我可以让我的自定义背景样式匹配它。更好的是,如果我可以修改它,我可以使它与我的匹配。
【讨论】:
【参考方案2】:今天我自己的工具也遇到了同样的问题。我认为您的问题与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 我没有测试上面的代码。但它应该可以工作)。
【讨论】:
以上是关于PyQt5 QTableView 用 Delegate 选择单元格背景的主要内容,如果未能解决你的问题,请参考以下文章
如何通过在 PyQt5 中按 Enter 键从 QTableView 获取数据
QTableView 和 QStyledItemDelegate 类的使用 (PyQt5)