如何为每个 QTableView 单元格支持两个单独的可双击值?
Posted
技术标签:
【中文标题】如何为每个 QTableView 单元格支持两个单独的可双击值?【英文标题】:How can I support two separate double-clickable values per QTableView cell? 【发布时间】:2014-06-02 13:20:36 【问题描述】:使用 PyQt5,我需要在 QTableView 中的每个单元格中显示两个值;基本上,每一列都必须分成两个逻辑子列。将鼠标指针悬停在某个值上方时,应突出显示其文本,而不是同一单元格中的其他值。类似地,应该可以对单元格内的单个值的双击做出反应。我该如何实现?
【问题讨论】:
【参考方案1】:我通过在QTableView 上实现一个细微的变化解决了这个问题,它利用QStyledItemDelegate 子类来绘制两个不同的值(突出显示或不突出显示)并检测它们中的每一个何时被双击。请注意,每个单元格的两个值在模型中表示为以分号分隔的字符串。
截图
从这个屏幕截图可以看出,左上角的 left 值被突出显示(由于鼠标悬停在其上方)。
代码
代码分为三个主要部分,表视图(QTableView 的子类)、委托(QStyledItemDelegate 的子类)和使用表视图的应用程序代码。
表格视图
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class TableView(QtWidgets.QTableView):
def __init__(self, parent):
super(TableView, self).__init__(parent)
self.__pressed_index = None
self.__entered_index = None
self.setItemDelegate(SplitCellDelegate(self))
self.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
for header in (self.horizontalHeader(), self.verticalHeader()):
header.installEventFilter(self)
def mouseDoubleClickEvent(self, event):
super(TableView, self).mouseDoubleClickEvent(event)
index = self.indexAt(event.pos())
if not index.isValid() or not self.__is_index_enabled(index) or self.__pressed_index != index:
me = QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, event.localPos(), event.windowPos(), event.screenPos(),
event.button(), event.buttons(), event.modifiers())
return
index_rel_pos = self.__get_index_rel_pos(event, index)
delegate = self.itemDelegate(index)
delegate.double_clicked(index, index_rel_pos)
def mousePressEvent(self, event):
super(TableView, self).mousePressEvent(event)
self.__pressed_index = self.indexAt(event.pos())
def mouseMoveEvent(self, event):
super(TableView, self).mouseMoveEvent(event)
if self.state() == self.ExpandingState or self.state() == self.CollapsingState or self.state() == self.DraggingState:
return
index = self.indexAt(event.pos())
if self.__entered_index is not None and index != self.__entered_index:
# We've left the currently entered index
self.itemDelegate(self.__entered_index).left(self.__entered_index)
self.__entered_index = None
if not index.isValid() or not self.__is_index_enabled(index):
# No index is currently hovered above
return
self.__entered_index = index
index_rel_pos = self.__get_index_rel_pos(event, index)
self.itemDelegate(index).mouse_move(index, index_rel_pos)
def leaveEvent(self, event):
super(TableView, self).leaveEvent(event)
self.__handle_mouse_exit()
def __handle_mouse_exit(self):
if self.__entered_index is None:
return
self.itemDelegate(self.__entered_index).left(self.__entered_index)
self.__entered_index = None
def eventFilter(self, obj, event):
if (obj is not self.horizontalHeader() and obj is not self.verticalHeader()) or \
event.type() not in (QtCore.QEvent.Enter,):
return super(TableView, self).eventFilter(obj, event)
self.__handle_mouse_exit()
return False
def __get_index_rel_pos(self, event, index):
"""Get position relative to index."""
# Get index' y offset
pos = event.pos()
x = pos.x()
y = pos.y()
while self.indexAt(QtCore.QPoint(x, y-1)) == index:
y -= 1
while self.indexAt(QtCore.QPoint(x-1, y)) == index:
x -= 1
return QtCore.QPoint(pos.x()-x, pos.y()-y)
def __is_index_enabled(self, index):
return index.row() >= 0 and index.column() >= 0 and index.model()
项目委托
class SplitCellDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent):
super(SplitCellDelegate, self).__init__(parent)
self.__view = parent
parent.setMouseTracking(True)
self.__hor_padding = 10
self.__above_value1 = self.__above_value2 = None
self.__rect = None
def paint(self, painter, option, index):
#print('Painting; width: '.format(option.rect.width()))
painter.setRenderHint(QtGui.QPainter.Antialiasing)
#print('Painting ,'.format(index.row(), index.column()))
rect = option.rect
# Copy the rect in case it changes
self.__rect = QtCore.QRect(option.rect)
if option.state & QtWidgets.QStyle.State_Selected:
painter.fillRect(rect, option.palette.highlight())
value1, value2 = self.__split_text(index)
value1_start, separator_start, value2_start = [x + rect.x() for x in self.__compute_offsets(index)]
if self.__above_value1 == index:
self.__set_bold_font(painter)
#print('Drawing value1 highlighted')
#print('Drawing \'\' from to '.format(self.__value1, value1_start, separator_start))
text_rect = QtCore.QRectF(0, rect.y(), rect.width(), rect.height())
painter.drawText(text_rect.translated(value1_start, 0), value1, QtGui.QTextOption(QtCore.Qt.AlignVCenter))
if self.__above_value1 == index:
painter.restore()
painter.drawText(text_rect.translated(separator_start, 0), '|', QtGui.QTextOption(QtCore.Qt.AlignVCenter))
if self.__above_value2 == index:
self.__set_bold_font(painter)
#print('Drawing value2 highlighted')
#else:
#print('Not drawing highlighted')
painter.drawText(text_rect.translated(value2_start, 0), value2, QtGui.QTextOption(QtCore.Qt.AlignVCenter))
if self.__above_value2 == index:
painter.restore()
def sizeHint(self, option, index):
value1, value2 = self.__split_text(index)
font = QtGui.QFont(self.__view.font())
font.setBold(True)
fm = QtGui.QFontMetrics(font)
return QtCore.QSize(self.__hor_padding*2 + fm.width('|'.format(value1, value2)),
15*2 + fm.height())
@staticmethod
def __set_bold_font(painter):
painter.save()
font = QtGui.QFont(painter.font())
font.setBold(True)
painter.setFont(font)
@staticmethod
def __split_text(index):
text = index.data(QtCore.Qt.DisplayRole).split(';')
value1 = text[0] + ' '
value2 = ' ' + text[1]
return value1, value2
def mouse_move(self, index, pos):
if self.__rect is None:
return
value1_start, separator_start, value2_start = self.__compute_offsets(index)
x = pos.x()
#print('Mouse move in cell: ( | )'.format(x, separator_start, value2_start))
if x < separator_start:
if self.__above_value1 == index:
return
self.__above_value1 = index
self.__above_value2 = None
#print('Above value1')
self.__repaint()
elif x >= value2_start:
if self.__above_value2 == index:
return
self.__above_value2 = index
self.__above_value1 = None
#print('Above value2')
self.__repaint()
elif self.__above_value1 is not None or self.__above_value2 is not None:
self.__above_value1 = self.__above_value2 = None
#print('Above separator')
self.__repaint()
def left(self, index):
#print('Index , left'.format(index.row(), index.column()))
self.__above_value1 = self.__above_value2 = None
self.__repaint()
def double_clicked(self, index, pos):
x = pos.x()
value1_start, separator_start, value2_start = self.__compute_offsets(index)
if x < separator_start:
print('Index , double-clicked at value 1'.format(index.row(), index.column()))
elif x >= value2_start:
print('Index , double-clicked at value 2'.format(index.row(), index.column()))
def __compute_offsets(self, index):
rect = self.__rect
value1, value2 = self.__split_text(index)
#print('Computing offsets; width: '.format(rect.width()))
font = QtGui.QFont(self.__view.font())
font.setBold(True)
fm = QtGui.QFontMetrics(font)
value2_start = rect.width() - fm.width(value2) - self.__hor_padding
separator_start = value2_start - fm.width('|')
value1_start = separator_start - fm.width(value1)
#print('Offsets for , are , , '.format(index.row(), index.column(), value1_start, separator_start, value2_start))
return value1_start, separator_start, value2_start
def __repaint(self):
# TODO: Repaint only cell in question
self.__view.viewport().repaint()
应用代码
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
table_view = self.__set_up_table()
w = QtWidgets.QWidget()
vbox = QtWidgets.QVBoxLayout(w)
vbox.addWidget(table_view)
self.setCentralWidget(w)
def __set_up_table(self):
rows = 4
cols = 4
table = QtGui.QStandardItemModel()
for row in range(rows):
l = [QtGui.QStandardItem('Row ;Column '.format(row, col)) for col in range(cols)]
table.appendRow(l)
table.setVerticalHeaderItem(row, QtGui.QStandardItem('Row '.format(row)))
for col in range(cols):
table.setHorizontalHeaderItem(col, QtGui.QStandardItem('Column '.format(col)))
table_view = TableView(self)
table_view.setModel(table)
table_view.setSortingEnabled(True)
table_view.resizeColumnsToContents()
return table_view
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.show()
app.exec_()
【讨论】:
以上是关于如何为每个 QTableView 单元格支持两个单独的可双击值?的主要内容,如果未能解决你的问题,请参考以下文章
如何为每次用户滚动时更新的每个 tableview 单元格添加一个计时器?
在 FirstVC 中选择单元格后,如何为 SecondVC 中的每个单元格调用按钮操作?
jface tableviewer,如何为每个单元格使用不同的单元格编辑器?
如何为 Objective-C 中的每个单元格设置不同的图像?