使用PyQt5,我需要在QTableView中的每个单元格中显示两个值。基本上,每一列都必须分为两个逻辑子列。将鼠标指针悬停在一个值上方时,应突出显示其文本,但不要突出显示同一单元格中的其他值。类似地,应该可以对单元格中各个值的双击做出反应。我该如何实施?
我通过在QTableView上实现一个细微的变化解决了该问题,该变化使用QStyledItemDelegate子类来绘制两个不同的值(突出显示或不突出显示),并检测何时双击它们。请注意,在模型中,每个像元的两个值表示为以分号分隔的字符串。
从该屏幕快照中可以看到,左上角的左值被突出显示(由于将鼠标悬停在其上方)。
该代码由三个主要部分组成:表格视图(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_()
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句