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