您可以将 QTableView 与自定义模型一起使用,并使用 setViewportMargins() 设置边距。
请注意,无论何时调用updateGeometries()(每次调整视图大小或标题中的某些内容时),所有 QAbstractItemView 子类都会重置边距,您需要重写该方法并再次重置边距。
然后,由于滚动区域中的绘画不能发生在视口之外,您必须创建小部件来绘制您需要的内容。
在以下实现中,这是通过使用事件过滤器来实现的,该过滤器将捕获这些小部件上的paintEvents,然后根据当前数据实际绘制内容。
请注意,为了正确实现这一点,以下几个方面很重要:
- 这些小部件必须在
__init__ 的最开始创建,因为即使设置了模型也会调用updateGeometries;
- 您需要将垂直滚动条
valueChanged连接到相应的update函数,以便内容始终正确更新;
- 这是一个非常简单的实现,没有考虑很多方面:例如,第八列和第九列之间没有“中间空间”;
class HexModel(QtCore.QAbstractTableModel):
# a simple model with random data contents
def __init__(self):
super().__init__()
self.randomData = [randrange(32, 128) for i in range(500)]
def columnCount(self, parent=None):
return 16
def rowCount(self, parent=None):
return len(self.randomData) // 16 + 1
def data(self, index, role=QtCore.Qt.DisplayRole):
if role == QtCore.Qt.DisplayRole:
try:
char = self.randomData[index.row() * 16 + index.column()]
return '{:02X}'.format(char)
except IndexError:
return None
elif role == QtCore.Qt.EditRole:
try:
return self.randomData[index.row() * 16 + index.column()]
except IndexError:
return None
elif role == QtCore.Qt.TextAlignmentRole:
return QtCore.Qt.AlignCenter
class HexEdit(QtWidgets.QTableView):
def __init__(self):
super().__init__()
self.offsetWidget = QtWidgets.QWidget(self)
self.asciiWidget = QtWidgets.QWidget(self)
self.offsetWidget.installEventFilter(self)
self.asciiWidget.installEventFilter(self)
self.setFont(QtGui.QFont('Courier'))
self.setModel(HexModel())
self.verticalHeader().hide()
self.horizontalHeader().hide()
self.horizontalHeader().setDefaultSectionSize(30)
self.horizontalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.Stretch)
self.verticalScrollBar().valueChanged.connect(self.offsetWidget.update)
self.verticalScrollBar().valueChanged.connect(self.asciiWidget.update)
def eventFilter(self, source, event):
if event.type() == event.Paint:
if source == self.offsetWidget:
self.paintOffset()
return True
elif source == self.asciiWidget:
self.paintAscii()
return True
return super().eventFilter(source, event)
def paintOffset(self):
top = self.indexAt(QtCore.QPoint())
if top.isValid():
top = top.row()
else:
top = 0
sectionHeight = self.verticalHeader().defaultSectionSize()
count = self.viewport().height() // sectionHeight
rect = QtCore.QRect(0, 0, self.offsetWidget.width(), sectionHeight)
offset = self.verticalHeader().offset() % sectionHeight
rect.translate(0, -offset)
qp = QtGui.QPainter(self.offsetWidget)
for i, row in enumerate(range(top, top + count + 1)):
qp.drawText(rect, QtCore.Qt.AlignVCenter|QtCore.Qt.AlignRight,
'{:08x}'.format(row * 16))
rect.translate(0, sectionHeight)
def paintAscii(self):
top = self.indexAt(QtCore.QPoint())
if top.isValid():
top = top.row()
else:
top = 0
sectionHeight = self.verticalHeader().defaultSectionSize()
count = self.viewport().height() // sectionHeight
rect = QtCore.QRect(0, 0, self.asciiWidget.width(), sectionHeight)
offset = self.verticalHeader().offset() % sectionHeight
rect.translate(0, -offset)
qp = QtGui.QPainter(self.asciiWidget)
model = self.model()
for i, row in enumerate(range(top, top + count + 1)):
chars = ''
for column in range(16):
char = model.index(row, column).data(QtCore.Qt.EditRole)
if char is None:
chars += '.'
else:
chars += chr(char)
qp.drawText(rect, QtCore.Qt.AlignVCenter|QtCore.Qt.AlignLeft, chars)
rect.translate(0, sectionHeight)
def updateGeometries(self):
super().updateGeometries()
charSize = self.fontMetrics().width('0')
margin = max(2, self.frameWidth() * 2)
self.offsetWidget.resize(charSize * 8 + margin, self.viewport().height())
self.asciiWidget.resize(charSize * 16 + margin, self.viewport().height())
self.setViewportMargins(
self.offsetWidget.width() + margin, 0,
self.asciiWidget.width() + margin, 0)
self.offsetWidget.move(margin, margin)
self.asciiWidget.move(self.viewport().geometry().right() + margin, margin)
def sizeHint(self):
hint = super().sizeHint()
margin = max(2, self.frameWidth() * 2)
width = self.offsetWidget.width() + self.asciiWidget.width()
width += margin * 4 + self.verticalScrollBar().width()
width += self.horizontalHeader().defaultSectionSize() * 16
hint.setWidth(width)
return hint