如果由同一应用程序触发,则丢弃 QClipboard::dataChanged() 信号

Discard QClipboard::dataChanged() signal if triggered by the same application

我在应用程序中使用 QClipboard,可以在其中复制和粘贴大型 3D 对象。粘贴操作可能会阻塞 GUI 一段时间,因为必须反序列化大量数据。

我想针对对象被复制并粘贴到同一应用程序的常见情况优化此功能 window。在那种情况下,我不需要系统范围的剪贴板,简单的内部函数可以存储和复制 c++ 对象而无需反序列化。

所以思路是:

1)当"Copy"被调用时,内部存储对象的副本,对象被序列化并放置在系统剪贴板中。设置一个标志以记住下一个粘贴操作应该直接使用存储的对象,而不是系统剪贴板。

2)当系统剪贴板已被另一个应用程序(可能是同一个程序,但另一个进程)修改时,将设置一个标志以了解下一个粘贴操作应该从系统剪贴板进行反序列化。

3) "Paste" 操作检查标志,并获取内部存储的对象,或者从系统剪贴板反序列化对象。

问题是 1)。每当我更改系统剪贴板时,都会触发 dataChanged() 信号。这是异步完成的,在调用 QClipboard::setData 之后很长时间。因此在调用 setData() 期间设置 blockSignals() 没有帮助。

有什么想法吗?

谢谢!

第一个问题是您不应该在 gui 线程中反序列化。您可以 运行 同时进行反序列化。

修复后,您可以针对内部剪贴板的情况进行优化。这可以使用标志来完成 - 不会阻塞信号。

下面是您如何解决这两个问题的草图。首先,我们有一个 Data class 来保存数据并且复制起来很昂贵 - 所以我们不让它被复制:

class Data {
   Q_DISABLE_COPY(Data) // presumably expensive to copy
public:
   //...
   QMimeData* serialize() { return new QMimeData; }
   static QSharedPointer<Data> deserialize(QMimeData*);
};
Q_DECLARE_METATYPE(QSharedPointer<Data>)

然后我们需要一种克隆 mime 数据的方法:

QMimeData* clone(const QMimeData *src) {
   auto dst = new QMimeData();
   for (auto format : src->formats())
      dst->setData(format, src->data(format));
   return dst;
}

最后,控制器对象具有由操作触发的 on_copyon_paste 方法。

跟踪分两个阶段完成:首先,副本将内部状态切换为 Copied。然后,当剪贴板指示数据已更改时,状态从 Copied 转换为 Ready

最后,如果状态为 Readyon_paste 将使用内部数据执行粘贴。否则,并发反序列化,反序列化完成后进行粘贴。

paste方法应该实现数据的实际粘贴。

class Class : public QObject {
   Q_OBJECT
   QSharedPointer<Data> m_data;
   enum { None, Copied, Ready } m_internalCopy = None;

   Q_SIGNAL void reqPaste(const QSharedPointer<Data> &);
   void paste(const QSharedPointer<Data> &);
   void onDataChanged() {
      m_internalCopy = m_internalCopy == Copied ? Ready : None;
   }
public:
   Q_SLOT void on_copy() {
      m_internalCopy = Copied;
      QScopedPointer<QMimeData> mimeData(m_data->serialize());
      QApplication::clipboard()->setMimeData(mimeData.data());
   }
   Q_SLOT void on_paste() {
      if (m_internalCopy == Ready)
         return paste(m_data);

      m_internalCopy = None;
      auto mimeData = clone(QApplication::clipboard()->mimeData());
      QtConcurrent::run([=]{
         emit reqPaste(Data::deserialize(mimeData));
         delete mimeData;
      });
   }
   Class() {
      qRegisterMetaType<QSharedPointer<Data>>();
      connect(QApplication::clipboard(), &QClipboard::dataChanged, this,
              &Class::onDataChanged);
      connect(this, &Class::reqPaste, this, &Class::paste);
   }
};

根据文档,如果 dataChanged() 是由当前进程本身引起的,我会假设 QClipboard::ownsClipboard() returns true

因此您可以检查连接到 dataChanged() 的插槽,例如忽略那个特定的调用。