2016-11-23 2 views
2

Я использую QClipboard в приложении, где большие 3D-объекты могут быть скопированы и вставлены. Операции вставки могут блокировать графический интерфейс в течение некоторого времени, так как многие данные должны быть де-сериализованы.Откажитесь от QClipboard :: dataChanged(), если он вызван тем же приложением

Я хотел бы оптимизировать это для частых случаев, когда объекты копируются и вставляются в одно и то же окно приложения. В этом случае мне не нужен системный буфер обмена, простые внутренние функции могут хранить и копировать объект C++ без необходимости десериализации.

Так идея:

1) Когда «Copy» называется, копия объекта хранится внутри, и объект сериализации и помещен в буфер обмена. Флаг установлен, чтобы помнить, что следующее действие вставки должно непосредственно принимать сохраненный объект, а не системный буфер обмена.

2) Когда системный буфер обмена был изменен другим приложением (возможно, с той же программой, но с другим процессом), флаг установлен, чтобы знать, что следующее действие вставки должно быть выполнено из системного буфера обмена с десериализацией.

3) Действие «Вставить» проверяет флаг и либо берет внутренне сохраненный объект, либо десериализует объект из системного буфера обмена.

Вопрос 1). Всякий раз, когда я меняю системный буфер обмена, срабатывает сигнал dataChanged(). И это выполняется асинхронно, долгое время после вызова QClipboard :: setData. Поэтому установка blockSignals() во время вызова setData() не помогает.

Любая идея?

Спасибо!

ответ

2

Из документации, я предположил бы, что QClipboard::ownsClipboard() возвращает true если dataChanged() вызывается самим текущим процессом.

Таким образом, вы можете проверить, что в слот подключен к dataChanged() и, например, игнорируйте этот конкретный вызов.

1

Первая проблема заключается в том, что вы не должны десериализоваться в потоке 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>) 

Тогда нам нужен способ, чтобы клонировать мим данные:

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

Наконец, объект контроллера, который имеет методы on_copy и on_paste, которые инициируются действиями.

Отслеживание выполняется в два этапа: во-первых, копия переключает внутреннее состояние на Copied. Затем, когда буфер обмена показывает, что данные были изменены, состояние переходит от Copied к Ready.

И, наконец, on_paste будет выполнять пасту, используя внутренние данные, если состояние Ready. В противном случае он будет десериализоваться одновременно и выполнить пасту после завершения десериализации.

Метод 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); 
    } 
}; 
+0

Полностью неблокирующая паста не является желательным поведением в большинстве случаев. Если операция вставки занимает много времени, обычно вы хотите, чтобы пользователь не взаимодействовал с графическим интерфейсом в течение этого времени. QProgressDialog с кнопкой отмены является imho намного лучше. – galinette

+0

@galinette Вы можете предлагать пользователю какое-либо поведение, если вы не блокируете цикл событий. «QProgressDialog» бесполезен, если вы не позволяете циклу событий работать, чтобы поддерживать пользовательский интерфейс. На самом деле, люди так часто ошибаются, что им пришлось добавить хак к методу setValue' диалога: он будет обрабатывать любые затяжные события для вас, даже если это означает потенциальное повторное внесение некоторого кода, который не был предназначен для быть повторно введенным. Очень легко определить, поддерживает ли приложение цикл своего события или нет. Те, которые обычно не требуют дополнительных затрат на ОС, делают диагностические снимки (OS X!). –

+0

@galinette Вы можете определенно показать модальный диалог прогресса, в то время как десериализация происходит в фоновом режиме. –

Смежные вопросы