PyQt5 中的顺序编程复制

Sequential programatic copying in PyQt5

我有一个显示小列表的 PyQt5 应用程序。它允许用户复制列表项。当用户复制列表项时,它会使用延迟渲染将对该项目的引用放置到剪贴板上。从剪贴板粘贴项目时,它会尝试切换选择并自动将下一个项目放入剪贴板。

延迟渲染第一次成功。但是,当我尝试清除或重新使用剪贴板时,我收到一个内部 Qt 错误,该错误会打印一条消息但不会传播到 Python。这发生在 Windows 10 上。虽然我正在寻找跨平台解决方案(因此是 Qt),但我目前只对在 Windows.

上解决这个问题感兴趣

以下是该应用的概览:

当我按下 Ctrl+C 时,所选项目被正确复制。然后我在记事本 window 中按 Ctrl+V。所选文本粘贴得很好。然后,self.copy 中的 QApplication.clipboard().clear()QApplication.clipboard().setMimeData(data) 行都 "silently" 失败,打印输出如下:

OleSetClipboard: Failed to set mime data (NULL) on clipboard: COM error 0xffffffff800401f0 CO_E_NOTINITIALIZED (Unknown error 0x0800401f0) (The parameter is incorrect.)
OleSetClipboard: Failed to set mime data (text/plain) on clipboard: COM error 0xffffffff800401f0 CO_E_NOTINITIALIZED (Unknown error 0x0800401f0) (The parameter is incorrect.)

我认为这与 Qt 在后台创建以支持 PyQt 接口的对象的生命周期有关,但我不知道如何修复它。

代码如下。我实现了一个只能处理文本的自定义 QMimeData class,并调用回调以响应 retreiveData。我将回调放在 Timer 上,以便在我们重新调整剪贴板用途之前可以返回并粘贴对象。这似乎没有什么区别:即使我更新了选择,粘贴也会正确进行,而且我无法访问剪贴板以获取另一个副本的原因更加明显。

from PyQt5.QtCore import Qt, QMimeData, QStringListModel, QVariant
from PyQt5.QtGui import QClipboard
from PyQt5.QtWidgets import QAbstractItemView, QApplication, QListView

from threading import Timer

class MyMimeData(QMimeData):
    FORMATS = {'text/plain'}

    def __init__(self, item, hook=None):
        super().__init__()
        self.item = item
        self.hook = hook

    def hasFormat(self, fmt):
        return fmt in self.FORMATS

    def formats(self):
        return list(self.FORMATS)

    def retrieveData(self, mime, type):
        if self.hasFormat(mime):
            if self.hook:
                self.hook()
            return QVariant(self.item)
        return QVariant()

class MyListView(QListView):
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_C and event.modifiers() & Qt.ControlModifier:
            self.copy()
        else:
            super().keyPressEvent(event)

    def toggleRow(self):
        current = self.selectedIndexes()[0]
        self.setCurrentIndex(self.model().index((1 - current.row()) % 2, current.column()))
        Timer(0.5, self.copy).start()

    def copy(self):
        item = self.selectedIndexes()[0].data()
        data = MyMimeData(item, self.toggleRow)
        # These are the lines that fail on the second round
        QApplication.clipboard().clear()
        QApplication.clipboard().setMimeData(data)

# Boilerplate to run the app
app = QApplication([])
model = QStringListModel(["First", "Second"])
view = MyListView()
view.setSelectionMode(QAbstractItemView.SingleSelection)
view.setModel(model)
view.show()

app.exec_()

我试过延长计时器的持续时间,但这并没有改变任何东西(当然除了延迟错误消息)。这并不奇怪,因为我预计在幕后会发生一些我不知道的范围界定问题。

我也尝试过使用 MyMimeData 的单个实例,并仅根据当前行更新它检索的内容。在这种情况下,只有第一行被一遍又一遍地粘贴,因为显然剪贴板一旦检索到特定格式的值就会缓存它。

平台规格:

这个灵感是我尝试回答Detecting paste in python

QObjects 的大部分属性都不是 thread-safe,因此您不应该修改未创建元素的线程中的元素。以上在 GUI 元素中更为关键。如果你想延迟,那么你应该使用 QTimer,它使用 Qt 事件循环实现功能:

QtCore.QTimer.singleShot(500, self.copy)