PyQt5中的顺序编程复制 [英] Sequential programatic copying in PyQt5

查看:449
本文介绍了PyQt5中的顺序编程复制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a PyQt5 application that shows a small list. It allows the user to copy list items. When the user copies a list item, it uses delayed rendering to place a reference to the item onto the clipboard. When the item is pasted from the clipboard, it attempts to toggle the selection and place the next item into the clipboard automatically.

延迟渲染第一次生效.但是,当我尝试清除或重复使用剪贴板时,出现内部Qt错误,该错误会打印一条消息,但不会传播到Python中.这是在Windows 10上发生的.虽然我正在寻找一种跨平台的解决方案(因此称为Qt),但我目前仅对在Windows上解决此问题感兴趣.

Delayed rendering works the first time. However, when I attempt to clear or re-use the clipboard, I get an internal Qt error which prints a message but does not propagate into Python. This is happening on Windows 10. While I am looking for a cross-platform solution (hence Qt), I am currently only interested in solving this on Windows.

以下是该应用的外观概述:

Here is an overview of what the app looks like:

当我按下 Ctrl + C 时,所选项目将被正确复制.然后,在记事本"窗口中按 Ctrl + V .选定的文本粘贴得很好.然后,self.copy中的行QApplication.clipboard().clear()QApplication.clipboard().setMimeData(data)都无声地"失败,并显示以下打印输出:

When I hit Ctrl+C, the selected item is copied correctly. I then hit Ctrl+V in a Notepad window. The selected text pastes just fine. Then, the lines QApplication.clipboard().clear() and QApplication.clipboard().setMimeData(data) in self.copy both "silently" fail with the following printouts:

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接口的情况下创建的对象的生命周期有关,但是我不知道如何解决它.

I believe that this has something to do with the lifetimes of the objects that Qt creates under the hood to support the PyQt interface, but I don't know how to fix it.

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

The code is below. I've implemented a custom QMimeData class that can only handle text, and calls a callback in response to retreiveData. I put the callback on a Timer so that the object can be returned and pasted before we repurpose the clipboard. This does not seem to make a difference: even if I update the selection, the paste happens correctly and it's a little more obvious as to why I can't access the clipboard for another copy.

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_()

我尝试延长计时器的持续时间,但这并没有改变任何东西(当然除了延迟错误消息外).这不足为奇,因为我希望在我不了解的情况下会发生一些范围界定问题.

I've tried extending the duration of the timer, but that does not change anything (besides delaying the error message of course). This is not surprising, as I expect there are some scoping issues occurring under the hood that I am not aware of.

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

I have also tried using a single instance of MyMimeData and just updating the content it retreives based on the current row. Only the first row gets pasted over and over in that case since apparently the clipboard caches the value for a particular format once it is retrieved.

平台规格:

  • 操作系统:Windows 10
  • Conda版本:conda 4.8.3
  • Python版本:Python 3.7.6
  • PyQt5.QtCore.QT_VERSION_STR:5.12.5
  • PyQt5.Qt.PYQT_VERSION_STR:5.12.3
  • OS: Windows 10
  • Conda Version: conda 4.8.3
  • Python Version: Python 3.7.6
  • PyQt5.QtCore.QT_VERSION_STR: 5.12.5
  • PyQt5.Qt.PYQT_VERSION_STR: 5.12.3

这样做的灵感是我试图回答在python中检测粘贴

The inspiration for this is my attempt to answer Detecting paste in python

推荐答案

QObjects的大多数属性不是线程安全,因此您不应修改未创建线程的线程中的元素.而以上内容在GUI元素中更为关键.如果要延迟,则应使用QTimer,它通过Qt eventloop实现该功能:

Most of the properties of QObjects are not thread-safe, so you should not modify elements from a thread in which it was not created. And the above is more critical in the GUI elements. If you want to delay then you should use QTimer which implements the functionality using the Qt eventloop:

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

这篇关于PyQt5中的顺序编程复制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆