Qt通过QAbstractItemDelegate.paint()绘制列表项可滚动可选文本
问题根源
你当前的实现逻辑从根本上不支持交互:
- 在
paint事件里临时创建QPlainTextEdit再调用render(),本质只是把控件当前的外观像截图一样绘制到列表项上,没有把控件的事件响应、状态管理挂载到视图层,所以既不能选中文字也不能拖动滚动条,画出来的滚动条只是静态图案。 - 每次触发重绘都新建、销毁控件,还频繁切换painter启停状态,自然会出现控件随机消失、位置对齐错乱、渲染闪烁的问题。
正确实现方案
不要在paint方法里做静态控件渲染,要通过委托的标准编辑器接口,给每个列表项挂载常驻的真实QPlainTextEdit控件,同时关闭默认的双击触发编辑逻辑,让控件初始化后直接显示,不需要进入编辑状态就能响应交互。
完整修正代码如下:
import os from typing import Union from PySide6 import QtWidgets as qtw from PySide6 import QtGui as qtg from PySide6 import QtCore as qtc ITEMS_SPACING = 10 THUMBNAIL_SIZE = (200, 200) class Delegate(qtw.QStyledItemDelegate): def paint(self, painter: qtg.QPainter, option: qtw.QStyleOptionViewItem, index: qtc.QModelIndex) -> None: # 仅绘制缩略图和选中背景,文本区域交给常驻的QPlainTextEdit处理 thumbnail: qtg.QImage = index.model().data(index, qtc.Qt.ItemDataRole.DecorationRole) if thumbnail is None: return # 绘制项选中状态背景 if option.state & qtw.QStyle.StateFlag.State_Selected: painter.fillRect(option.rect, option.palette.highlight()) # 绘制左侧缩略图 painter.drawImage( qtc.QRect(option.rect.topLeft(), qtc.QSize(*THUMBNAIL_SIZE)), thumbnail ) def sizeHint(self, option: qtw.QStyleOptionViewItem, index: qtc.QModelIndex) -> qtc.QSize: return qtc.QSize(option.rect.width(), THUMBNAIL_SIZE[1]) def createEditor(self, parent: qtw.QWidget, option: qtw.QStyleOptionViewItem, index: qtc.QModelIndex) -> qtw.QWidget: # 创建真实可交互的文本编辑控件,禁止在paint中临时创建 editor = qtw.QPlainTextEdit(parent) editor.setReadOnly(False) # 去掉默认边框,和列表项视觉融合 editor.setFrameShape(qtw.QFrame.Shape.NoFrame) editor.setVerticalScrollBarPolicy(qtc.Qt.ScrollBarPolicy.ScrollBarAsNeeded) editor.setHorizontalScrollBarPolicy(qtc.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) return editor def setEditorData(self, editor: qtw.QWidget, index: qtc.QModelIndex) -> None: # 从模型加载文本填充到编辑器 text = index.model().data(index, qtc.Qt.ItemDataRole.DisplayRole) editor.setPlainText(text) def setModelData(self, editor: qtw.QWidget, model: qtc.QAbstractItemModel, index: qtc.QModelIndex) -> None: # 需要支持文本修改时实现对应逻辑,纯只读场景可空实现 model.setData(index, editor.toPlainText(), qtc.Qt.ItemDataRole.DisplayRole) def updateEditorGeometry(self, editor: qtw.QWidget, option: qtw.QStyleOptionViewItem, index: qtc.QModelIndex) -> None: # 统一计算文本控件位置,从根源避免对齐错乱 editor.setGeometry( option.rect.left() + THUMBNAIL_SIZE[0] + ITEMS_SPACING, option.rect.top(), option.rect.width() - THUMBNAIL_SIZE[0] - ITEMS_SPACING * 2, THUMBNAIL_SIZE[1] ) class Model(qtc.QAbstractListModel): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) # 过滤非图片文件,避免加载无效资源 self._thumbnails = [ qtg.QImage(filename) for filename in os.listdir('.') if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif')) ] self._descriptions = ["this is text " * 5000 for _ in range(len(self._thumbnails))] def rowCount(self, _: qtc.QModelIndex) -> int: return len(self._thumbnails) def data(self, index: qtc.QModelIndex, role: qtc.Qt.ItemDataRole) -> Union[str, qtg.QImage, None]: if not index.isValid(): return None if role == qtc.Qt.ItemDataRole.DisplayRole: return self._descriptions[index.row()] elif role == qtc.Qt.ItemDataRole.DecorationRole: return self._thumbnails[index.row()] return None def flags(self, index: qtc.QModelIndex) -> qtc.Qt.ItemFlag: # 给列表项添加可编辑标记,支持挂载常驻编辑器 return super().flags(index) | qtc.Qt.ItemFlag.ItemIsEditable class MainWindow(qtw.QMainWindow): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) delegate = Delegate(self) self._model = Model() self._view = qtw.QListView(self) self._view.setSpacing(ITEMS_SPACING) self._view.setModel(self._model) self._view.setItemDelegate(delegate) # 关键配置:关闭双击触发编辑逻辑,让编辑器可以常驻显示 self._view.setEditTriggers(qtw.QAbstractItemView.EditTrigger.NoEditTriggers) self.setCentralWidget(self._view) self.resize(800, 600) self.show() # 首次加载时为所有列表项打开持久化编辑器,无需手动触发 for row in range(self._model.rowCount(qtc.QModelIndex())): index = self._model.index(row, 0) self._view.openPersistentEditor(index) if __name__ == "__main__": app = qtw.QApplication() mw = MainWindow() app.exec()
关键改动说明
- 委托基类从
QAbstractItemDelegate更换为QStyledItemDelegate,默认会处理系统样式、选中状态等基础渲染逻辑,减少自定义绘制的兼容问题。 - 彻底移除paint中临时创建控件、截图渲染的错误逻辑,paint仅负责绘制缩略图和选中背景。
- 用持久化编辑器机制给每个列表项挂载真实的
QPlainTextEdit控件,控件原生支持文本选中、滚动、复制等交互,不需要额外实现事件逻辑。 - 所有控件位置计算统一收敛到
updateEditorGeometry方法,不会出现位置偏移、对齐错误。 - 每个列表项的文本控件仅创建一次,不会在每次重绘时反复创建销毁,从根源解决闪烁、控件随机消失的问题。
内容的提问来源于stack exchange,提问作者オパラ
相关产品推荐
相关产品推荐

