Перетаскивание и отпускание
Перетаскивание и отпускание предоставляет простой визуальный механизм, который пользователи могут использовать для передачи информации между и внутри приложений. Перетаскивание и отпускание по функциональности аналогично механизму вырезания и вставки буфера обмена.
В данном документе описывается базовый механизм перетаскивания и отпускания и описывается подход, используемый для его включения в пользовательские элементы управления. Многие элементы управления 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 , он должен сохранить собственную ссылку на исходную информацию; источнику не нужно выполнять дальнейшую обработку данных. Наиболее распространенное использование действий перетаскивания и отпускания — выполнение перемещения внутри одного и того же виджета; см. раздел Действия отпускания для получения дополнительной информации об этой функции.
Другое важное использование действий перетаскивания — при использовании типа ссылки, такого как текст/список-uri, где перетаскиваемые данные фактически являются ссылками на файлы или объекты.
Добавление новых типов перетаскивания и отпускания
Перетаскивание и отпускание не ограничиваются текстом и изображениями. Любой тип информации может быть передан в операции перетаскивания и отпускания. Чтобы перетаскивать информацию между приложениями, приложения должны иметь возможность указывать друг другу, какие форматы данных они могут принимать и какие они могут генерировать. Это достигается с помощью типов 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, &QClipboard::dataChanged,
this, &ClipWindow::updateClipboard); Слоту, подключённому к этому сигналу, можно прочитать данные в буфере обмена с помощью одного из типов MIME, которые могут быть использованы для их представления:
void ClipWindow::updateClipboard()
{
QStringList formats = clipboard->mimeData()->formats();
for (const auto &format : 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-6.1/dnd.html