Spec-Zone.ru › Qt 5.9

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

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

Этот документ описывает базовый механизм перетаскивания и опускания и описывает подход, используемый для его включения в пользовательские элементы управления. Операции перетаскивания и опускания также поддерживаются многими элементами управления 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 перетаскивания и опускания 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/qt-5.9/dnd.html

Spec-Zone.ru

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