Перетаскивание и Опускание
Перетаскивание и опускание обеспечивает простой визуальный механизм, который пользователи могут использовать для передачи информации между и внутри приложений. Перетаскивание и опускание аналогично механизму вырезания и вставки буфера обмена.
В этом документе описан базовый механизм перетаскивания и опускания и описан подход, используемый для его включения в пользовательские элементы управления. Многие элементы управления 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 Перетаскивание и опускание 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, он должен сохранить собственную ссылку на исходную информацию; исходный объект не нуждается в дальнейшей обработке данных. Наиболее распространенное использование действий перетаскивания и опускания — выполнение перемещения внутри того же виджета; см. раздел Действия опускания для получения дополнительной информации об этой функции.
Другое важное использование действий перетаскивания — использование типа ссылки, такого как 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/archives/qt-5.6/dnd.html