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

Posted

技术标签:

【中文标题】如果由同一应用程序触发,则丢弃 QClipboard::dataChanged() 信号【英文标题】:Discard QClipboard::dataChanged() signal if triggered by the same application 【发布时间】:2016-11-23 14:51:28 【问题描述】:

我在应用程序中使用 QClipboard,可以复制和粘贴大型 3D 对象。由于需要对大量数据进行反序列化,因此粘贴操作可能会阻塞 GUI 一段时间。

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

所以想法是:

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

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

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

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

有什么想法吗?

谢谢!

【问题讨论】:

【参考方案1】:

如果dataChanged() 是由当前进程本身引起的,我会假设QClipboard::ownsClipboard() 返回true 从文档中。

所以你可以在连接到dataChanged() 的插槽中检查它,例如忽略该特定调用。

【讨论】:

【参考方案2】:

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

一旦解决了这个问题,您就可以针对剪贴板在内部的情况进行优化。这可以使用标志来完成 - 不会阻塞信号。

下面简要介绍了如何解决这两个问题。首先,我们有一个 Data 类来保存数据并且复制成本很高 - 所以我们不会让它被复制:

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);
   
;

【讨论】:

在大多数情况下,完全非阻塞粘贴不是所需的行为。如果粘贴操作需要很长时间,您通常希望防止用户在此期间与 GUI 交互。带有取消按钮的 QProgressDialog 更好。 @galinette 只要不阻止事件循环,您就可以向用户提供任何行为。如果您不让事件循环运行以保持 UI 响应,QProgressDialog 将毫无用处。事实上,人们经常出错,以至于他们不得不在对话框的 setValue 方法中添加一个 hack:它会为你处理任何挥之不去的事件,即使这意味着可能重新输入一些本不应该这样做的代码被重新输入。很容易判断应用程序是否保持其事件循环响应。那些通常不会导致操作系统执行诊断快照(OS X!)的额外成本。 @galinette 您绝对可以在后台进行反序列化时显示模态进度对话框。

以上是关于如果由同一应用程序触发,则丢弃 QClipboard::dataChanged() 信号的主要内容,如果未能解决你的问题,请参考以下文章

如果播放由 postMessage 触发,则不会触发 Youtube 的 onStateChange

场景测试法

如果 SharePoint 上的一个特定文件发生更改,则触发操作

如何检测同一文件的输入类型=文件“更改”?

詹金斯 - 如果开始新的构建,则中止运行构建

Rx Swift 丢弃 .do(onNext: ) 我如何让它触发?