【问题标题】:Widgets structure小部件结构
【发布时间】:2021-03-02 22:41:17
【问题描述】:

我正在尝试使用 Qt 框架(在 pyqt 下进行快速测试)制作一个十六进制编辑器。

我的目标是拥有一个类似于 hexed.it 等知名十六进制编辑器的 GUI

十六进制编辑截图:

就像你在图片中看到的那样,有 3 个部分:

  • 线的偏移量(左)
  • 实际的十六进制数据(中心)
  • 数据的 ASCII 表示(右)

我希望能够单独隐藏/删除所有这些部分。

我知道如何创建自定义小部件、模型、视图或委托,但我不知道应该为这 3 个部分创建什么。

我应该创建 3 个单独的视图并尝试确保它们的大小相同吗? 我应该创建 1 个大小部件并在其中绘制所有内容吗? 还是有其他我没有想到的解决方案?

基本上,您将如何构建所有内容?

【问题讨论】:

  • 我建议你改写这篇文章的标题,因为它非常模糊和笼统,而标题应该总是尽量清楚地概括问题的内容。

标签: python pyqt


【解决方案1】:

您可以将 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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-15
    • 1970-01-01
    • 2013-12-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多