Перетаскивание и Опускание
Перетаскивание и опускание предоставляет простой визуальный механизм, который пользователи могут использовать для передачи информации между и внутри приложений. Перетаскивание и опускание аналогично механизму вырезания и вставки буфера обмена.
Этот документ описывает базовый механизм перетаскивания и опускания и описывает подход, используемый для его включения в пользовательские элементы управления. Операции перетаскивания и опускания также поддерживаются многими элементами управления 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 Drag and Drop .
Перетаскивание
Для начала перетаскивания создайте объект 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()
{
mimeTypeCombo->clear();
QStringList formats = clipboard->mimeData()->formats();
if (formats.isEmpty())
return;
for (const auto &format : formats) {
QByteArray data = clipboard->mimeData()->data(format);
// ...
} Сигнал selectionChanged() может быть использован на X11 для мониторинга выделения мышью.
Примеры
Взаимодействие с другими приложениями
На X11 используется общедоступный протокол XDND, в то время как на Windows Qt использует стандарт OLE, а Qt для macOS использует Cocoa Drag Manager. На 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.2/dnd.html