Russian Qt Forum

Qt => Уроки и статьи => Тема начата: xintrea от Декабрь 04, 2008, 19:55



Название: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: xintrea от Декабрь 04, 2008, 19:55
Как передавать через буфер обмена произвольные типы данных

Рано или поздно, при разработке более-менее крупного проекта, возникает задача передачи через буфер обмена самодельных данных. В литературе и документации по Qt этот вопрос освещен слабо, и данный урок должен восполнить этот пробел.

Давайте посмотрим, что написано по вопросу передачи собственных типов данных в книге М.Шлее "Qt4. Профессиональное программирование на C++".

Цитировать
"...может понадобиться перетаскивать и принимать свои собственные типы данных, например звуковые данные. Как поступать в подобных ситуациях? Для этих случаев, в классе QMimeData определен метод setData(), в который первым параметром нужно передать строку, характеризующую тип данных, а вторым сами данные в объекте класса QByteArray. Но можно поступить и иначе - унаследовать класс QMimeData и перезаписать методы formats() и retrieveData() ..."

Городить огород с методом setData() и с преобразованием наших данных в QByteArray (и обратно) мы не будем. А сделам более правильно, а именно разберемся, что скрывается за фразой "унаследовать класс QMimeData и перезаписать методы formats() и retrieveData()".
 
Нам нужно будет создать класс, унаследованный от класса QMimeData. С этим вопросов возникнуть не должно. Метод formats() просто возвращает список строк, содержащий текстовые идентификаторы данных, которые данный класс может обработать. (Мы можем сами себе придумать и использовать такие строки, например "myapplication/sounddata", "myapplication/matrixdata" и.т.д. Это нас оградит хотябы от того, что после копирования данных в буфер и попытки вставки в текстовый редактор, в редактор не будет запихиваться бинарный мусор. Блокировка произойдет потому, что текстовый редактор способен работать с данными "text/plain", а всякие "myapplication/sounddata" ему неизвестны, и поэтому данные с таким идентификатором в буфере обмена будут проигнорированы).

В данном классе можно создать свойство (или несколько свойств), которые будут содержать наши передаваемые данные. Заполнять данные в свойствах можно отдельными методами. То есть, не нужно передавать всю требуемую структуру данных в виде каким-то образом сгенерированного QByteArray. Можно также написать метод, который примет данные в виде определенной пользователем структуры, и запомнит ее (в свойстве класса, которое имеет тип данной структуры). То есть, внесение данных крупной проблемы не представляет.

Но дело осложняется тем, что метод retrieveData() должен возвращать все данные из буфера единовременно, и возвращать данные надо в виде типа QVariant. Вот какой прототип, согласно документации, должен быть у перегружаемого нами метода retrieveData()

Код
C++ (Qt)
QVariant retrieveData ( const QString & mimeType, QVariant::Type type ) const


Если бы мы возвращали данные в виде QByteArray, то особых проблем бы не возникло - QByteArray прекрасно передается через QVariant. Но тогда нужно будет городить низкоуровневую упаковку и разбор данных из такого массива. А это ничем не отличается от работы с буфером обмена через метод setData().

Поэтому, первая наша задача - разобраться, как передавать через QVariant самодельную структуру данных. Как только мы это поймем, сделать все остальное не составит большого труда.

Не буду отсылать к документации, и объясню на пальцах, как передавать произвольные структуры через QVariant. Предположим, у нас есть некая структура

Код
C++ (Qt)
// Структура данных, которая будет передаваться через буфер обмена
struct clipb_struct
{
int number; // Некое число
QString stringline; // Некая строка
QMap<QString, QString> namevaluetable; // Некая таблица вида "имя переменной" "значение"
};

Чтобы передавать через QVariant данные с типом clipb_struct, надо этот тип просто-напросто зарегистрировать в программе. Делается это так

Код
C++ (Qt)
Q_DECLARE_METATYPE(clipb_struct);

само собой, данный код нужно размещать после описания структуры. После этого действия, структура clipb_struct будет передаваться через QVariant.


Ну а дальше все просто. Пишется класс, унаследованный от QMimeData. В классе делаем переменную (свойство) типа clipb_struct. Пишем методы (или метод), которые будут заполнять поля данной переменной. Переопределяем метод retrieveData(), он как раз и должен возвращать переменную типа clipb_struct. Можем так же написать методы-хелперы, которые будут возвращать значения полей переменной типа clipb_struct.

Работу с буфером обмена можно организовать так.

Внесение информации в буфер обмена

Код
C++ (Qt)
// Создается ссылка на буфер обмена
QClipboard *pastebuf=QApplication::clipboard();
 
// Создается объект, который будет помещен в буфер обмена и вносятся в него данные
clipb *cb=new clipb();
cb->set_number(100);
cb->set_stringline("Hello Qt");
cb->ins_namevalue("name","Article about scintific");
cb->ins_namevalue("author","Aristotel");
cb->ins_namevalue("text","Scintific is paradox area of mind");
cb->print();
 
// Объект с данными помещается в буфер обмена
pastebuf->setMimeData( cb );

Извлечение данных из буфера обмена

Код
C++ (Qt)
// Создается ссылка на буфер обмена
QClipboard *getbuf=QApplication::clipboard();
 
// Создается объект для приема данных
const clipb *cb2;
 
// Данные из буфера вставляются в принимающий объект
cb2=qobject_cast<const clipb *>(getbuf->mimeData());

после таких действий в объекте cb2 в переменной типа clipb_struct будут лежать переданные через буфер данные. И их можно извлекать далее с помощью методов-хелперов, или с помощью одного метода, который просто возращает переменную типа clipb_struct.


UPD: При вызове вставки данных из буфера, в первую очередь нужно проверять, в каком формате находятся данные в буфере. Это необходимо, чтобы не произошло сегфолта при попытке вставить данные одного формата в объект другого формата. Проверить формат данных очень просто.

Код
C++ (Qt)
// Если буфер обмена не содержит данные нужного формата,
// вставку из буфера делать нельзя
if(!(QApplication::clipboard()->mimeData()->hasFormat("mytetra/records"))) return;


Чтобы все стало более понятно,  прикрепляю готовый пример. В примере демонстрируется передача соственного типа данных через буфер обмена. Пример предельно простой, нет никаких кнопочек, только главное окно программы. В примере происходит копирование в буфер, и, сразу, извлечение из буфера. Вся последовательность действий пишется в консоль.

Проверено на Qt 4.4.1, Linux.


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: panAlexey от Сентябрь 26, 2009, 19:14
Что насчет копирования структуры это между приложениями, в которых есть её декларация?
Сейчас сижу и думаю, каким путем пойти. Структуры у мну сложные...


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: xintrea от Октябрь 02, 2009, 00:18
По идее, должно передаваться между двумя приложениями, если (в нашем примере) структура clipb_struct будет одинаковой в обоих приложениях.


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: panAlexey от Октябрь 04, 2009, 15:32
Ну, доделай пример так, что-бы приложение при старте проверяло буфер обмена и если там структура, то цепляло её.


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: jurtal от Март 28, 2012, 16:52
Сделал как описано выше под виндой и Qt4.7.1.  При копировании объекта clipb между приложениями получаю нулевой указатель cb. В пределах одного приложения - все нормально. В чем ошибка?

void Widget::onPaste()
{
    const QClipboard *clipboard = QApplication::clipboard();
    const QMimeData *mimeData = clipboard->mimeData();
    if (! mimeData->hasFormat("myapp/multidata"))
   {
       return;
    }

    const clipb *cb =qobject_cast<const clipb *>(mimeData);
}   


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: jurtal от Март 29, 2012, 10:34
Попробовал приаттаченный пример - тоже не работает. Видимо урок только в пределах одного приложения справедлив...


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: kambala от Март 29, 2012, 13:06
исходники не качал, но в примере кода в первом посте нету задания миме-типа для объекта типа clipb - может поэтому?


Название: Re: Урок: Как передавать через буфер обмена пр
Отправлено: Hammer от Апрель 12, 2013, 15:17
А как насчет передачи массива из таких структур, например? В моем случае нужно чтобы копипастились графические элементы и не по одному, а кучно.

Хотя, нафига мне буфер? ) Я ж и сам могу


Название: Re: Урок: Как передавать через буфер обмена произвольные типы данных
Отправлено: xintrea от Апрель 15, 2013, 20:52
исходники не качал, но в примере кода в первом посте нету задания миме-типа для объекта типа clipb - может поэтому?

Ну как же не задается:

Код:
clipb_formats << "myapp/multidata";

...

// Перегруженный метод QMimeData
QStringList clipb::formats() const
{
 return clipb_formats;
}

Так что дело наверно в другом. Я сейчас несколько отошел от Qt, так что помочь не могу.