Последовательное программирование c копирования в PyQt5 - PullRequest
1 голос
/ 14 апреля 2020

У меня есть приложение PyQt5, которое показывает небольшой список. Это позволяет пользователю копировать элементы списка. Когда пользователь копирует элемент списка, он использует отложенную визуализацию, чтобы поместить ссылку на элемент в буфер обмена. Когда элемент вставляется из буфера обмена, он пытается переключить выделение и автоматически помещает следующий элемент в буфер обмена.

Отложенный рендеринг работает в первый раз. Однако, когда я пытаюсь очистить или повторно использовать буфер обмена, я получаю внутреннюю ошибку Qt, которая печатает сообщение, но не распространяется на Python. Это происходит в Windows 10. Пока я ищу кроссплатформенное решение (следовательно, Qt), в настоящее время меня интересует только решение этого в Windows.

. Вот краткий обзор того, что Приложение выглядит так:

enter image description here

Когда я нажимаю Ctrl + C, выбранный элемент копируется правильно. Затем я нажимаю Ctrl + V в окне блокнота. Выделенный текст вставляется просто отлично. Затем строки QApplication.clipboard().clear() и QApplication.clipboard().setMimeData(data) в self.copy обе «молча» терпят неудачу со следующими распечатками:

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, который может обрабатывать только текст, и вызывает обратный вызов в ответ на 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_()

I Я пытался увеличить продолжительность таймера, но это ничего не меняет (кроме задержки сообщения об ошибке, конечно). Это неудивительно, так как я ожидаю, что есть некоторые проблемы с областями видимости, которые я не знаю.

Я также пытался использовать один экземпляр MyMimeData и просто обновлять содержимое, которое он получает на основе в текущем ряду. В этом случае снова и снова вставляется только первая строка, поскольку очевидно, что буфер обмена кэширует значение для определенного формата после его извлечения.

Характеристики платформы:

  • ОС: 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

Вдохновением для этого является моя попытка ответить Обнаружение пасты в python

1 Ответ

1 голос
/ 14 апреля 2020

Большинство свойств объектов QObject не являются поточно-безопасными , поэтому не следует изменять элементы из потока, в котором он не был создан. И вышесказанное более критично для элементов GUI. Если вы хотите отложить, вы должны использовать QTimer, который реализует функциональность, используя Qt eventl oop:

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