调整 HTML 编辑器在 QTableView 中的外观和行为

Adjust HTML editor appearance and behaviour in QTableView

我想做的:在我的 table 的两列中都有 HTML 个单元格,无论是在渲染还是在编辑时。 Col 1 应该是完整的 editable。编辑 Col 0 中的单元格时,应该可以 select 文本,包括使用键盘“编辑器工具包”键,例如 Ctrl-Shift-Right(前进 selection 一个词)等., 但应该无法编辑文本。

在这两列中,我希望编辑器尽可能与渲染器没有区别。

这是一个 MRE:

class TableViewDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self):
        super().__init__()
        self.editor_for_col_0 = None 
        self.editor_for_other_cols = None 
        self.doc = QtGui.QTextDocument()
    
    def createEditor(self, parent, option, index):
        if index.column() == 0:
            if self.editor_for_col_0 == None:
                self.editor_for_col_0 = QtWidgets.QTextEdit(parent)
                self.editor_for_col_0.setReadOnly(True)
            return self.editor_for_col_0
        else:
            if self.editor_for_other_cols == None:
                self.editor_for_other_cols = QtWidgets.QTextEdit(parent)
            return self.editor_for_other_cols
     
    def setEditorData(self, editor, index):
        cell_text = index.model().data(index, QtCore.Qt.DisplayRole)
        editor.setHtml(cell_text)            
 
    def destroyEditor(self, editor, index):
        editor.clear()
        
    def paint(self, painter, option, index):
        options = QtWidgets.QStyleOptionViewItem(option)
        self.doc.setDefaultFont(options.font)
        self.initStyleOption(options, index)
         
        painter.save()

        self.doc.setTextWidth(options.rect.width())                
        self.doc.setHtml(options.text)
        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
        self.doc.documentLayout().draw(painter, ctx)
        
        painter.restore()
        
    def sizeHint(self, option, index):
        self.initStyleOption(option, index)
        if option.text == '':
            return super().sizeHint(option, index)
        self.doc.setHtml(option.text)
        self.doc.setTextWidth(option.rect.width())
        self.doc.setDefaultFont(option.font)
        self.doc.setDocumentMargin(0)
        return QtCore.QSize(int(self.doc.idealWidth()), int(self.doc.size().height()))

class TableViewModel(QtCore.QAbstractTableModel):
    def __init__(self):
        super().__init__()
        self._data = [
            ['Lorem <em>ipsum dolor</em> sit amet, <strong>consectetur adipiscing</strong> elit.', 
             'Curabitur eu <strong>nulla</strong> ut <em>lacus bibendum</em> interdum.'],
            ['Vivamus <em>ultricies <strong>eleifend nulla</strong></em> eget pharetra.', 
             'Fusce <strong>aliquam magna</strong> a <em>sem placerat consectetur</em>.',],
       ]
    
    def rowCount(self, *args):
        return len(self._data)
    
    def columnCount(self, *args):
        return 2
    
    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            return self._data[index.row()][index.column()]

    def flags(self, index):
        result = super().flags(index)
        return QtCore.Qt.ItemFlag.ItemIsEditable | result
    
class TableView(QtWidgets.QTableView):
    def __init__(self):
        super().__init__()
        self.setItemDelegate(TableViewDelegate()) 
        self.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers)
        
class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Frozen HTML editor MRE')
        layout = QtWidgets.QVBoxLayout()
        self.table_view = TableView()
        layout.addWidget(self.table_view)
        self.table_view.setModel(TableViewModel())
        
        self.table_view.setColumnWidth(0, 200)
        self.table_view.setColumnWidth(1, 200)
        
        centralwidget = QtWidgets.QWidget(self)
        centralwidget.setLayout(layout)
        self.setCentralWidget(centralwidget)
        self.setGeometry(QtCore.QRect(400, 400, 600, 400))
        self.table_view.resizeRowsToContents()
                
app = QtWidgets.QApplication([])
main_window = MainWindow()
main_window.show()
sys.exit(app.exec())    

有2个问题:

  1. 当在两列中输入编辑时,编辑器会出现一个细蓝框,更糟糕的是,会出现某种导致出现滚动条的边缘、填充或边距。我在编辑器上试过 setContentMargins 但这似乎没有任何效果。

  2. 尽管可以在 Col 0 编辑器中使用鼠标 select 文本,但由于 setReadOnly(True),正常的“编辑器工具包”键扩展 selection 不再工作了。我想知道是否有另一种方法可以实现“只读但-selectable-with-editor-kit”编辑器?

PS OS 是 W10.

编辑

ekhumoro 几乎解决了这个问题。

我找到了摆脱蓝色边框的方法:

self.editor[...].setFrameStyle(QtWidgets.QFrame.NoFrame)

如果 setDocumentMargin() 设置为相同的值,例如0,对于编辑器和 sizeHint,它 大部分 坚如磐石:当编辑开始时,文本甚至不会移动 1 个像素。

(注意一旦在第 1 列的编辑器中,您可以按 Escape 停止编辑,然后按 Ctrl-向下箭头移动到下面的单元格)。

但是...如果文本很长,事情就会变得很糟糕。如果将以下第三行添加到 table,您就会明白我的意思:

['Nam fermentum a velit vel euismod. Quisque ut mollis nibh. Nulla aliquam placerat tortor, eget mattis eros tincidunt id. Nunc auctor eros feugiat, molestie tellus a, congue eros. ', 
         'Suspendisse tempor finibus tempus. Morbi fermentum rutrum lectus, in malesuada mauris tincidunt maximus. Fusce id elit rutrum, congue diam a, imperdiet ex. Vivamus sagittis purus eleifend, feugiat urna sit amet, feugiat erat. Nullam imperdiet ligula elit. Pellentesque lobortis efficitur metus, ornare ullamcorper magna interdum imperdiet. Praesent fermentum feugiat erat, dignissim molestie sem convallis eget. Proin varius nisi quis enim convallis elementum. Mauris ac vulputate leo. Cras ac nisi eu velit mattis maximus. In interdum interdum dui, et blandit turpis tristique non. Integer molestie bibendum turpis non feugiat.',],

(您可能想像这样更改 setGeometryself.setGeometry(QtCore.QRect(400, 400, 600, 1000))

... 然后您可以看到在编辑器中出现了自动换行,而在呈现的单元格中却没有。这反过来会导致出现滚动条,从而使所有内容都变成垃圾。

我正在尝试追查此问题的根源。对我来说,它看起来像 1 个像素的差异。

仍然是陌生人:如果你转到右下角的单元格,在 QTreeView 构造函数中使用我的 setEditTriggers,这将立即开始编辑。但是,如果您按 Escape 停止编辑,然后按 F2 再次开始编辑,则自动换行不会发生!

嗯……

可以通过 text interaction flags:

控制键盘行为
self.editor_for_col_0.setTextInteractionFlags(
    QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard)

内部填充可以通过document margin:

设置
self.editor_for_col_0.document().setDocumentMargin(0)

蓝框问题似乎是特定于平台的,因为我无法在我的 Linux 系统上重现它。这些问题中有一些建议的解决方案:

  • Hide the border of the selected cell in qtablewidget in pyqt?
  • Qt QTableWidget's gray dotted border around a selected cell

最有希望达到您的目的似乎是调整委托中的 style-option state flags。您可能需要对各种 focus/selection 标志组合进行一些试验,以获得您的系统所需的行为。

如前所述,ekhumoro 解决了大部分问题。

正如我在编辑中提到的,出现在 Windows10 中的蓝框可以通过以下方式消除:

self.editor_[...].setFrameStyle(QtWidgets.QFrame.NoFrame)

我还找到了一个可能的解决方案来解决“虚假的”自动换行问题,但仍如编辑中所述不一致地出现。

self.editor_[...].setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

...这似乎有效。而且,由于 col1 是可编辑的,这意味着您必须设计一些策略来应对当内容开始从底部流出视图时发生的情况(例如,重新调整行高...)。