Spec-Zone.ru › Qt 5.13

Перетаскивание и отбрасывание

Перетаскивание и отбрасывание предоставляет простой визуальный механизм, который пользователи могут использовать для передачи информации между приложениями и внутри приложения. Перетаскивание и отбрасывание аналогично механизму вырезания и вставки буфера обмена.

Этот документ описывает базовый механизм перетаскивания и отбрасывания и описывает подход, используемый для его включения в пользовательские элементы управления. Многие элементы управления Qt, такие как представления элементов и фреймворк представления графики, а также элементы управления редактированием для Qt Widgets и Qt Quick, также поддерживают операции перетаскивания и отбрасывания. Дополнительную информацию о представлениях элементов и фреймворке представления графики можно найти в Использование перетаскивания и отбрасывания с представлениями элементов и Фреймворк представления графики.

Классы перетаскивания и отбрасывания

Эти классы занимаются перетаскиванием и отбрасыванием и необходимым кодированием и декодированием типа MIME.

QDrag

Поддержка передачи данных перетаскивания и отбрасывания на основе MIME

QDragEnterEvent

Событие, которое отправляется виджету, когда действие перетаскивания и отбрасывания входит в него

QDragLeaveEvent

Событие, которое отправляется виджету, когда действие перетаскивания и отбрасывания покидает его

QDragMoveEvent

Событие, которое отправляется во время выполнения действия перетаскивания и отбрасывания

QDropEvent

Событие, которое отправляется, когда действие перетаскивания и отбрасывания завершается

Настройка

Объект 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, &QClipboard::dataChanged,
            this, &ClipWindow::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/qt-5.13/dnd.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API