Spec-Zone.ru › Qt 5.14

Перетаскивание

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

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

Spec-Zone.ru

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