You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

QTableView中HTML编辑器样式统一及只读可选功能实现问题

解决方案

1. 编辑器边框与边距问题

你找到的去边框方案正确,边距问题不要使用setContentMargins,直接对齐编辑器内部文档和渲染用文档的边距,同时关闭滚动条即可:

# 在createEditor初始化编辑器时添加以下代码
editor.setFrameStyle(QtWidgets.QFrame.NoFrame)
editor.document().setDocumentMargin(0) # 和paint、sizeHint中的文档边距完全一致
editor.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
editor.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

2. 列0只读但支持选中快捷键

setReadOnly(True)会默认过滤部分文本操作快捷键,建议自定义只读文本框,只拦截修改内容的操作,保留所有选中、导航、复制相关快捷键:

class ReadOnlyTextEdit(QtWidgets.QTextEdit):
    def keyPressEvent(self, event):
        # 允许的修饰键组合
        allowed_modifiers = (
            QtCore.Qt.Key_Control | QtCore.Qt.Key_Shift,
            QtCore.Qt.Key_Control,
            QtCore.Qt.Key_Shift,
            QtCore.Qt.NoModifier
        )
        # 允许的按键:导航、选中、复制
        allowed_keys = (
            QtCore.Qt.Key_Left, QtCore.Qt.Key_Right, 
            QtCore.Qt.Key_Up, QtCore.Qt.Key_Down,
            QtCore.Qt.Key_Home, QtCore.Qt.Key_End,
            QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown,
            QtCore.Qt.Key_C
        )
        if (event.modifiers() in allowed_modifiers and event.key() in allowed_keys) or event.text().isspace():
            super().keyPressEvent(event)
        else:
            # 忽略所有修改内容的按键操作
            event.ignore()
    
    def insertFromMimeData(self, source):
        # 禁止粘贴操作
        pass

列0创建编辑器时直接使用上述自定义类即可,无需设置setReadOnly(True),所有选中类快捷键均可正常使用。

3. 长文本换行不一致问题

该问题根源有两个:

  • 复用全局QTextDocument实例,不同单元格计算尺寸和渲染时互相干扰
  • 编辑器初始化时先设置HTML再设置宽度,导致第一次换行计算错误,第二次进入编辑时宽度正确所以换行恢复正常

修复代码

class TableViewDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self):
        super().__init__()
        self.editor_for_col_0 = None 
        self.editor_for_other_cols = None 

    def createEditor(self, parent, option, index):
        if index.column() == 0:
            if self.editor_for_col_0 is None:
                self.editor_for_col_0 = ReadOnlyTextEdit(parent)
                self.editor_for_col_0.setFrameStyle(QtWidgets.QFrame.NoFrame)
                self.editor_for_col_0.document().setDocumentMargin(0)
                self.editor_for_col_0.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
                self.editor_for_col_0.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            return self.editor_for_col_0
        else:
            if self.editor_for_other_cols is None:
                self.editor_for_other_cols = QtWidgets.QTextEdit(parent)
                self.editor_for_other_cols.setFrameStyle(QtWidgets.QFrame.NoFrame)
                self.editor_for_other_cols.document().setDocumentMargin(0)
                self.editor_for_other_cols.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
                self.editor_for_other_cols.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
            return self.editor_for_other_cols
     
    def setEditorData(self, editor, index):
        cell_text = index.model().data(index, QtCore.Qt.DisplayRole)
        # 先设置文档宽度再设置HTML,保证换行计算正确
        editor.document().setTextWidth(editor.width())
        editor.setHtml(cell_text)            

    def updateEditorGeometry(self, editor, option, index):
        # 先设置编辑器尺寸和单元格完全一致
        editor.setGeometry(option.rect)
        # 同步文档宽度和单元格宽度
        editor.document().setTextWidth(option.rect.width())
        super().updateEditorGeometry(editor, option, index)
        
    def paint(self, painter, option, index):
        options = QtWidgets.QStyleOptionViewItem(option)
        self.initStyleOption(options, index)
        # 每次渲染新建文档,避免全局实例互相干扰
        doc = QtGui.QTextDocument()
        doc.setDefaultFont(options.font)
        doc.setDocumentMargin(0)
        doc.setTextWidth(options.rect.width())                
        doc.setHtml(options.text)
        
        painter.save()
        options.text = ""
        options.widget.style().drawControl(QtWidgets.QStyle.CE_ItemViewItem, options, painter)
        painter.translate(options.rect.left(), options.rect.top())
        clip = QtCore.QRectF(0, 0, options.rect.width(), options.rect.height())
        painter.setClipRect(clip)
        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        ctx.clip = clip
        doc.documentLayout().draw(painter, ctx)
        painter.restore()
        
    def sizeHint(self, option, index):
        self.initStyleOption(option, index)
        if not option.text:
            return super().sizeHint(option, index)
        # 每次计算尺寸新建文档,避免干扰
        doc = QtGui.QTextDocument()
        doc.setDefaultFont(option.font)
        doc.setDocumentMargin(0)
        col_width = option.rect.width() if option.rect.width() > 0 else self.parent().columnWidth(index.column())
        doc.setTextWidth(col_width)
        doc.setHtml(option.text)
        return QtCore.QSize(int(doc.idealWidth()), int(doc.size().height()))

4. 初始化自动进入编辑问题

AllEditTriggers包含了CurrentChanged触发条件,当前项初始化变更时就会自动进入编辑,修改触发规则为你需要的组合即可:

# TableView的__init__中替换setEditTriggers代码
self.setEditTriggers(
    QtWidgets.QAbstractItemView.DoubleClicked | 
    QtWidgets.QAbstractItemView.EditKeyPressed |
    QtWidgets.QAbstractItemView.SelectedClicked
)

内容的提问来源于stack exchange,提问作者mike rodent

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 09:36:01