Spec-Zone.ru › Qt 5.15

Перетаскивание и Опускание

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

В этом документе описывается базовый механизм перетаскивания и опускания и описывается подход, используемый для его включения в пользовательские элементы управления. Многие элементы управления 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 Перетащить, 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. В 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.15/dnd.html

Spec-Zone.ru

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