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

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,提问作者オパラ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 19:48:31