Перетаскивание
Перетаскивание предоставляет простой визуальный механизм, который пользователи могут использовать для передачи информации между приложениями и внутри приложения. Перетаскивание по функции аналогично механизму вырезания и вставки буфера обмена.
В этом документе описывается базовый механизм перетаскивания и описывается подход к его включению в пользовательские элементы управления. Многие элементы управления Qt, такие как просмотр элементов и фреймворк просмотра графики, а также элементы управления редактированием для Qt Widgets и Qt Quick, также поддерживают операции перетаскивания. Дополнительную информацию о просмотре элементов и фреймворке просмотра графики можно найти в Использование перетаскивания с элементами просмотра и Фреймворк просмотра графики.
Классы перетаскивания и сбрасывания
Эти классы занимаются перетаскиванием и необходимым кодированием и декодированием типа MIME.
Поддержка передачи данных перетаскивания и сбрасывания на основе MIME |
|
Событие, которое отправляется виджету, когда действие перетаскивания входит в него |
|
Событие, которое отправляется виджету, когда действие перетаскивания покидает его |
|
Событие, которое отправляется во время выполнения действия перетаскивания |
|
Событие, которое отправляется, когда действие перетаскивания завершено |
Настройка
Объект QStyleHints предоставляет некоторые свойства, связанные с операциями перетаскивания:
- QStyleHints::startDragTime() описывает количество миллисекунд, в течение которого пользователь должен удерживать кнопку мыши над объектом, прежде чем начнется перетаскивание.
- QStyleHints::startDragDistance() указывает, на какое расстояние пользователь должен переместить курсор, удерживая кнопку мыши, прежде чем движение будет интерпретировано как перетаскивание.
-
QStyleHints::startDragVelocity() указывает, насколько быстро (в пикселях/секунду) пользователь должен перемещать курсор, чтобы начать перетаскивание. Значение
0означает, что такого ограничения нет.
Эти величины предоставляют разумные значения по умолчанию, совместимые с основой системы окон, которые вы можете использовать, если предоставляете поддержку перетаскивания в своих элементах управления.
Перетаскивание и сбрасывание в Qt Quick
Остальная часть документа в основном посвящена реализации перетаскивания и сбрасывания в C++. Для использования перетаскивания и сбрасывания внутри сцены Qt Quick прочитайте документацию по элементам Qt Quick Drag, DragEvent и DropArea, а также примеры Перетаскивание и сбрасывание в Qt Quick.
Перетаскивание
Чтобы начать перетаскивание, создайте объект QDrag и вызовите его функцию exec(). В большинстве приложений разумно начинать операцию перетаскивания и сбрасывания только после нажатия кнопки мыши и перемещения курсора на определенное расстояние. Однако самый простой способ включить перетаскивание из виджета — переопределить функцию mousePressEvent() виджета и начать операцию перетаскивания и сбрасывания:
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton
&& iconLabel->geometry().contains(event->pos())) {
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(commentEdit->toPlainText());
drag->setMimeData(mimeData);
drag->setPixmap(iconPixmap);
Qt::DropAction dropAction = drag->exec();
...
}
} Хотя пользователю может потребоваться некоторое время для завершения операции перетаскивания, по отношению к приложению функция exec() является блокирующей функцией, которая возвращается с одним из нескольких значений. Они указывают, как завершилась операция, и подробно описаны ниже.
Обратите внимание, что функция exec() не блокирует основной цикл событий.
Для виджетов, которым необходимо различать щелчки мышью и перетаскивания, полезно переопределить функцию mousePressEvent() виджета, чтобы записать начальную позицию перетаскивания:
void DragWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
dragStartPosition = event->pos();
} Позже, в mouseMoveEvent(), мы можем определить, следует ли начинать перетаскивание, и создать объект перетаскивания для обработки операции:
void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if ((event->pos() - dragStartPosition).manhattanLength()
< QApplication::startDragDistance())
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
...
} Этот конкретный подход использует функцию QPoint::manhattanLength() для получения приблизительной оценки расстояния между тем, где произошел щелчок мышью, и текущим положением курсора. Эта функция жертвует точностью ради скорости и обычно подходит для этой цели.
Сбрасывание
Чтобы иметь возможность принимать данные, сброшенные на виджет, вызовите setAcceptDrops(true) для виджета и переопределите обработчики событий dragEnterEvent() и dropEvent().
Например, следующий код включает события сброса в конструкторе подкласса QWidget, что делает возможным реализацию обработчиков событий сброса:
Window::Window(QWidget *parent)
: QWidget(parent)
{
...
setAcceptDrops(true);
} Функция dragEnterEvent() обычно используется для информирования Qt о типах данных, которые принимает виджет. Вы должны переопределить эту функцию, если хотите получить либо QDragMoveEvent, либо QDropEvent в своих переопределениях dragMoveEvent() и dropEvent().
Следующий код показывает, как можно переопределить dragEnterEvent(), чтобы сообщить системе перетаскивания и сбрасывания, что мы можем обрабатывать только обычный текст:
void Window::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/plain"))
event->acceptProposedAction();
} Функция dropEvent() используется для распаковки данных, сброшенных, и обработки их подходящим для вашего приложения способом.
В следующем коде текст, предоставленный в событии, передается в QTextBrowser, а QComboBox заполняется списком типов MIME, используемых для описания данных:
void Window::dropEvent(QDropEvent *event)
{
textBrowser->setPlainText(event->mimeData()->text());
mimeTypeCombo->clear();
mimeTypeCombo->addItems(event->mimeData()->formats());
event->acceptProposedAction();
} В этом случае мы принимаем предложенное действие без проверки того, что это такое. В реальном приложении может потребоваться возвращаться из функции dropEvent() без принятия предложенного действия или обработки данных, если действие не актуально. Например, мы можем выбрать игнорирование действий Qt::LinkAction, если мы не поддерживаем ссылки на внешние источники в нашем приложении.
Переопределение предложенных действий
Мы также можем игнорировать предложенное действие и выполнить какое-либо другое действие с данными. Для этого мы вызываем функцию setDropAction() объекта события с предпочтительным действием из Qt::DropAction перед вызовом accept(). Это гарантирует, что вместо предложенного действия используется замещающее действие сброса.
Для более сложных приложений переопределение функций dragMoveEvent() и dragLeaveEvent() позволит вам сделать некоторые части ваших виджетов чувствительными к событиям сброса и предоставит вам больший контроль над перетаскиванием и сбрасыванием в вашем приложении.
Наследование сложных виджетов
Некоторые стандартные виджеты Qt обеспечивают собственную поддержку перетаскивания. При наследовании от этих виджетов может потребоваться переопределить dragMoveEvent() в дополнение к dragEnterEvent() и dropEvent(), чтобы предотвратить предоставление базовым классом обработки перетаскивания по умолчанию и обработать любые специальные случаи, которые вас интересуют.
Действия перетаскивания и сбрасывания
В самом простом случае целевой объект действия перетаскивания и сбрасывания получает копию перетаскиваемых данных, а источник решает, удалить ли оригинал. Это описывается действием CopyAction. Цель также может выбрать обработку других действий, в частности, действий MoveAction и LinkAction. Если источник вызывает QDrag::exec(), и он возвращает MoveAction, источник отвечает за удаление любых исходных данных, если он решит это сделать. Объекты QMimeData и QDrag, созданные виджетом-источником, не должны удаляться — они будут уничтожены Qt. Цель отвечает за получение владения данными, отправленными в ходе операции перетаскивания и сбрасывания; это обычно делается путем сохранения ссылок на данные.
Если целевой объект понимает действие LinkAction , он должен сохранить свою собственную ссылку на исходную информацию; источнику не нужно выполнять дальнейшую обработку данных. Наиболее распространенное использование действий перетаскивания — при выполнении перемещения внутри одного и того же виджета; см. раздел Действия сбрасывания для получения дополнительной информации об этой функции.
Другое важное применение действий перетаскивания — при использовании типа ссылки, например, text/uri-list, где перетаскиваемые данные фактически являются ссылками на файлы или объекты.
Добавление новых типов перетаскивания и сбрасывания
Перетаскивание и сбрасывание не ограничиваются текстом и изображениями. Любой тип информации может быть передан в ходе операции перетаскивания и сбрасывания. Чтобы перетаскивать информацию между приложениями, приложения должны иметь возможность указывать друг другу, какие форматы данных они могут принимать и какие они могут генерировать. Это достигается с помощью типов MIME. Объект QDrag, созданный источником, содержит список типов MIME, которые он использует для представления данных (в порядке от наиболее подходящего к наименее подходящему), а целевой объект сброса использует один из них для доступа к данным. Для распространенных типов данных удобные функции обрабатывают используемые типы MIME прозрачно, но для пользовательских типов данных необходимо явно указать их.
Чтобы реализовать действия перетаскивания и сбрасывания для типа информации, который не покрывается удобными функциями QDrag, в первую очередь необходимо искать подходящие существующие форматы: Уполномоченная организация по присвоению номеров в Интернете (IANA) предоставляет иерархический список типов MIME медиа в Институте информатики (ISI). Использование стандартных типов MIME максимизирует взаимозаменяемость вашего приложения с другим программным обеспечением сейчас и в будущем.
Чтобы поддержать дополнительный тип медиа, просто установите данные в объекте QMimeData с помощью функции setData(), указав полный тип MIME и QByteArray, содержащую данные в соответствующем формате. Следующий код берет пиксель из метки и сохраняет его как файл Portable Network Graphics (PNG) в объекте QMimeData:
QByteArray output;
QBuffer outputBuffer(&output);
outputBuffer.open(QIODevice::WriteOnly);
imageLabel->pixmap()->toImage().save(&outputBuffer, "PNG");
mimeData->setData("image/png", output); Конечно, в этом случае мы могли бы просто использовать setImageData() вместо этого, чтобы предоставить данные изображения в различных форматах:
mimeData->setImageData(QVariant(*imageLabel->pixmap()));
Подход с QByteArray по-прежнему полезен в этом случае, так как он обеспечивает больший контроль над объемом данных, хранимых в объекте QMimeData.
Обратите внимание, что пользовательские типы данных, используемые в представлениях элементов, должны быть объявлены как метаобъекты, и для них должны быть реализованы операторы потоков.
Действия при падении
В модели буфера обмена пользователь может вырезать или скопировать исходную информацию, а затем вставить её позже. Аналогично, в модели перетаскивания пользователь может перетащить копию информации или перетащить саму информацию в новое место (переместив её). Модель перетаскивания имеет дополнительную сложность для программиста: программа не знает, хочет ли пользователь вырезать или скопировать информацию, пока операция не будет завершена. Это часто не имеет значения при перетаскивании информации между приложениями, но внутри приложения важно проверить, какое действие при падении было использовано.
Мы можем переопределить mouseMoveEvent() для виджета и начать операцию перетаскивания с комбинацией возможных действий при падении. Например, мы можем захотеть убедиться, что перетаскивание всегда перемещает объекты в виджете:
void DragWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!(event->buttons() & Qt::LeftButton))
return;
if ((event->pos() - dragStartPosition).manhattanLength()
< QApplication::startDragDistance())
return;
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData(mimeType, data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
...
} Действие, возвращаемое функцией exec(), может по умолчанию быть CopyAction, если информация опускается в другое приложение, но если она опускается в другой виджет в том же приложении, мы можем получить другое действие при падении.
Предлагаемые действия при падении могут быть отфильтрованы в функции dragMoveEvent() виджета. Однако можно принять все предлагаемые действия в dragEnterEvent() и позволить пользователю решить, какие из них принять позже:
void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
} Когда падение происходит в виджете, вызывается обработчик функции dropEvent(), и мы можем обработать каждое возможное действие по очереди. Сначала мы обрабатываем операции перетаскивания и опускания внутри одного и того же виджета:
void DragWidget::dropEvent(QDropEvent *event)
{
if (event->source() == this && event->possibleActions() & Qt::MoveAction)
return; В этом случае мы откажемся от обработки операций перемещения. Каждый тип действия при падении, который мы принимаем, проверяется и обрабатывается соответствующим образом:
if (event->proposedAction() == Qt::MoveAction) {
event->acceptProposedAction();
// Process the data from the event.
} else if (event->proposedAction() == Qt::CopyAction) {
event->acceptProposedAction();
// Process the data from the event.
} else {
// Ignore the drop.
return;
}
...
} Обратите внимание, что мы проверили отдельные действия при падении в приведенном выше коде. Как упоминалось выше в разделе о переопределении предлагаемых действий, иногда необходимо переопределить предлагаемое действие при падении и выбрать другое из набора возможных действий при падении. Для этого необходимо проверить наличие каждого действия в значении, предоставленном методом возможных действий события possibleActions(), установить действие при падении с помощью setDropAction() и вызвать accept().
Прямоугольники падения
Функция dragMoveEvent() виджета может использоваться для ограничения падений определёнными частями виджета, принимая предлагаемые действия при падении только тогда, когда курсор находится внутри этих областей. Например, следующий код принимает любые предлагаемые действия при падении, когда курсор находится над дочерним виджетом (dropFrame):
void Window::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("text/plain")
&& event->answerRect().intersects(dropFrame->geometry()))
event->acceptProposedAction();
} dragMoveEvent() также может использоваться, если вам нужно предоставить визуальную обратную связь во время операции перетаскивания, прокрутить окно или выполнить любые другие соответствующие действия.
Буфер обмена
Приложения также могут обмениваться данными, помещая их в буфер обмена. Для доступа к этому необходимо получить объект QClipboard из объекта QApplication.
Класс QMimeData используется для представления данных, которые передаются в буфер обмена и из него. Для помещения данных в буфер обмена можно использовать удобные функции setText(), setImage() и setPixmap() для общих типов данных. Эти функции похожи на те, что есть в классе QMimeData, за исключением того, что они также принимают дополнительный аргумент, который управляет местом хранения данных: если указан Clipboard, данные помещаются в буфер обмена; если указан Selection, данные помещаются в выделение мышью (только для X11). По умолчанию данные помещаются в буфер обмена.
Например, мы можем скопировать содержимое QLineEdit в буфер обмена с помощью следующего кода:
QGuiApplication::clipboard()->setText(lineEdit->text(), QClipboard::Clipboard);
Данные с разными типами MIME также могут быть помещены в буфер обмена. Создайте объект QMimeData и установите данные с помощью функции setData() так, как описано в предыдущем разделе; этот объект затем можно поместить в буфер обмена с помощью функции setMimeData().
Класс QClipboard может уведомлять приложение об изменениях в содержащихся в нём данных посредством сигнала dataChanged(). Например, мы можем отслеживать буфер обмена, подключив этот сигнал к слоту в виджете:
connect(clipboard, SIGNAL(dataChanged()), this, SLOT(updateClipboard()));
Слот, подключенный к этому сигналу, может считывать данные из буфера обмена, используя один из типов MIME, который может быть использован для их представления:
void ClipWindow::updateClipboard()
{
QStringList formats = clipboard->mimeData()->formats();
QByteArray data = clipboard->mimeData()->data(format);
...
} Сигнал selectionChanged() может использоваться на X11 для отслеживания выделения мышью.
Примеры
- Перетаскиваемые значки
- Перетаскиваемый текст
- Место падения
- Магниты холодильника
- Пазл с перетаскиванием
Взаимодействие с другими приложениями
В X11 используется общедоступный протокол XDND, в то время как в Windows Qt использует стандарт OLE, а Qt для macOS использует менеджер перетаскивания Cocoa. В X11 XDND использует MIME, поэтому перевод не требуется. API Qt одинаков независимо от платформы. В Windows приложения, поддерживающие MIME, могут обмениваться данными, используя имена форматов буфера обмена, которые являются типами MIME. Уже некоторые приложения Windows используют соглашения о именовании MIME для своих форматов буфера обмена.
Пользовательские классы для перевода собственных форматов буфера обмена могут быть зарегистрированы путём переопределения QWinMime в Windows или QMacPasteboardMime в macOS.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-5.12/dnd.html