Home · All Classes · Main Classes · Grouped Classes · Modules · Functions

Drag and Drop

Drag and drop представляет собой простой механизм, с помощью которого пользователи могут перемещать информацию межде и внутри приложений. (В литературе это называется "моделью прямого управления".) Drag and drop подобен функциям вырезания и вставки буфера обмена.

Данный документ описывает базовый механизм drag and drop и обозначает подход, подходящий для реализации в собственных виджетах. Операции Drag and drop также поддерживаются представлениями Qt; более подробная информация доступна в документе Использование Drag and Drop с представлениями.

Конфигурирование

Объект QApplication предоставляет некоторые функции связанные с операциями drag and drop:

Для этих свойств предусмотрены разумные значения по умолчанию, которые Вы можете использовать при поддержке Вашим виджетом drag and drop.

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

Для начала перетаскивания нужно создать объект QDrag и вызвать его функцию start(). В большинстве приложений хорошо начинать операцию drag and drop только после нажатия кнопки мыши и перемещения указателя мыши на некоторую дистанцию. Однако самый простой способ начать перетаскивание состоит в повторной реализации mousePressEvent() виджета и начинать операцию drag and drop:

    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->start();
        ...
        }
    }

Хотя пользователю, для завершения операции перетаскивания, может потребоваться некоторое время, приложение заинтересовано в возврате из функции start() одного из нескольких значений. Они указывают, как завершается операция и подробно описаны ниже.

Для виджетов, которые должны понимать различие между щелчком мышью и перетаскиванием, полезно повторно реализовать функцию виджета 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->start(Qt::CopyAction | Qt::MoveAction);
        ...
    }

Самый специфичный подход состоит в использовании функции QPoint::manhattanLength() для получения грубой оценки расстояния между точкой, где произошло нажатие кнопки мыши и текущим положением указателя мыши. Эта функция позволяет грубо и быстро оценить расстояние и обычно подходит для этих целей.

Отпускание

Для установления возможности отпускания перетаскиваемых данных над виджетом, вызовите для виджета setAcceptDrops(true) и заново реализуйте метод-обработчик сообщиний. Например, следующий код, в конструкторе подкласса QWidget, делает доступными сообщения об отпускании:

    Window::Window(QWidget *parent)
        : QWidget(parent)
    {
        ...
        setAcceptDrops(true);
    }

Реализовав только dragEnterEvent() и dropEvent() можно позволить перетаскивание в виджет. Функция dragEnterEvent() обычно используется, чтобы сообщить Qt о типах данных, которые виджет готов принять; dropEvent() используется для распаковки отпускаемых данных и обработать их способом, подходящим для Вашего приложения.

Следующий код показывает, как может быть реализовано dragEnterEvent() для указания системе drag and drop, что мы можем обработать только простой текст:

    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.

Для более сложных приложений, повторная реализация dragMoveEvent() и dragLeaveEvent() позволит Вам сделать некоторые части Ваших виджетов чуствительными к сообщениям об отпускании и даст Вам больший контроль над drag and drop в Вашем приложении.

Действия Drag and Drop

В простейшем случае, адресат действия drag and drop получает копию перетаскиваемых данных, а источник решает, удалить-ли оригинал. Такой порядок предписывается действием CopyAction. Адресат также может захотеть обрабатывать и другие действия, определены действия MoveAction и LinkAction. Если источник вызвал QDrag::start(), и оно вернуло MoveAction, то источник, если он этого хочет, сам отвечает за удаление исходных данных. Объекты QMimeData и QDrag созданные виджетом-источником и не должны быть удалены - они будут разрушены Qt. Адресат отвечает за приняние владения даннными, переданными с помощью действий drag and drop; обычно это делают, сохраняя ссылки на данные.

Если адресат понимает действие LinkAction action, то он должен сам хранить ссылку на исходные данные; источник не должен выполнять никакой дальнейшей обработки информации. Наиболее распространенное действие drag and drop - это перемещение данных в пределах одного виджета; для получения более продробной информации об этом режиме, см. раздел Действия Отпускания.

Другой основной способ использования перетаскивания - это когда используетс тип ссылок, подобный text/uri-list, при котором перетаскиваемая информация фактически является ссылкой на файлы или объекты.

Добавление Новых Типов Drag and Drop

Drag and drop не ограничено перетаскиванием текста и изображений. С помощью операции drag and drop могут быть перемещены данные любых типов. Для перемещения данных между приложениями, приложения должны сообщить информацию о том, какие другие форматы данных они могут принять и какие они могут передать. Это достигается с помощью использования типов MIME. Объект QDrag, построенный источником, содержит список типов MIME, используемых для представления данных (отсортированный от более соответствующего к менее соответствующему), а адресат используется один из них для получения доступа к данным. Для общих типов данных функции прозрачно обрабатывают типы MIME, но для пользовательских типов необходимо их объявить явно.

Для реализации действий drag and drop для типов информации, не охваченных функциями QDrag, первым, и наиболее важным, шагом должен стать поиск существующих форматов, которые наиболее соответствуют перетаскиваемому: Internet Assigned Numbers Authority (IANA) предоставляет иерархический список типов MIME media от Information Sciences Institute (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(), но вышеприведенный способ предоставляет больший контроль над форматом изображения.

Действия Отпускания

В модели буфера обмена пользователь может вырезать или скопировать исходные данные, чтобы позже их вставить. Также и в модели drag and drop model: пользователь может перетаскивать копию информации или саму информацию в другое место (перемещая ее). Модель drag and drop содержит дополнительную сложность для программиста: Программа не знает, хочет ли пользователь вырезать или скопировать информацию, пока операция не завершена. Часто эти операции не различаются при выполнении перетаскивания информации между приложениями, то внутри приложения важно проверить, какое из действий отпускания использовано.

Мы можем повторно реализовать для виджета mouseMoveEvent(), и начать операцию drag and drop в комбинации с возможным действием отпускания. Например, мы может захотеть гарантировать, что объекты всегда перемещаются внутри виджета:

    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->start(Qt::CopyAction | Qt::MoveAction);
        ...
    }

Действие, возвращаемое функцией start(), по умолчанию равно CopyAction если перетаскивание происходит в другое приложение. Но, если перетаскивание происходит в другой виджет этого-же приложения, мы может получить различные действия отпускания.

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

    void DragWidget::dragMoveEvent(QDragMoveEvent *event)
    {
        event->acceptProposedAction();
    }

Когда в виджете происходит отпускание, то вызывается функция-обработчик dropEvent(), и мы можем, в свою очередь, иметь дело с каждым возможным действием. Сперва мы определяем, является ли операция drag and drop перемещением внутри одного виджета:

    void DragWidget::dropEvent(QDropEvent *event)
    {
        if (event->source() == this && event->possibleActions() & Qt::MoveAction)
            return;

В нашем случае мы отказываемся иметь дело с операцией перемещения. Каждый тип действия отпускания, который мы принимаем, проверяется и обрабатывается соответственно:

        if (event->possibleActions() == Qt::MoveAction) {
            event->acceptProposedAction();
            // Process the data from the event.
        } else if (event->possibleActions() == Qt::CopyAction) {
            event->acceptProposedAction();
            // Process the data from the event.
        } else {
            // Ignore the drop.
            return;
        }
        ...
    }

Обратите внимание, что в вышеприведенном коде мы выполняли проверку для каждого действия отпускания. Часто бывает полезнее проверить присутствие каждого действия в значении, предоставляемом функцией possibleActions(), и установить принятое действие в функции сообщения setDropAction().

Прямоугольники Отпускания

dragMoveEvent() может использоваться для ограничения перемещения некоторыми частями виджета, позволяя принять отпускание виджета только в случае, если курсор находится в определенных областях. Например, в следующем примере принимаются любые действия отпускания, если курсор находится над дочерним виджетом (dropFrame):

    void Window::dragMoveEvent(QDragMoveEvent *event)
    {
        if (event->mimeData()->hasFormat("text/plain")
            && event->answerRect().intersects(dropFrame->geometry()))

            event->acceptProposedAction();
    }

dragMoveEvent() также может использоваться, если Вы хотите ограничить область перемещения видимой областью, или связать его с прокручиванием окна.

Буфер Обмена

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

        clipboard = QApplication::clipboard();

Класс QMimeData используется для представления информации, перемещаемой в/из буфер обмена. При помещении информации в буфер обмена, Вы можете использовать функции удобства setText(), setImage() и setPixmap() для работы с общими типами данных. Эти функции подобны тем, что можно найти в классе QMimeData за исключением того, то они принимают дополнительный аргумент, указывающий, где располагаются данные: Если определено Clipboard, то информация располагается в буфере обмена; если определено Selection, то информация располагается в выделенном мышью объекте (только в X11). По умолчанию, информация помещается в буфер обмена.

Например, Вы можете скопировать содержимое QLineEdit в буфер обмена с помощью следующего кода:

        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/Mac используется Carbon Drag Manager. В X11, XDND использует MIME, поэтому нет никакой необходимости в конвертации. Qt API не зависит от платформы. В Windows, приложение, знакомое с MIME, может сообщить буферу обмена тип MIME формата данных. Уже некоторые приложения Windows используют имена MIME для своих форматов данных в буфере обмена. Внутренне Qt имеет средства для перевода собственных форматов буфера обмена в/из типы MIME. Эти интерфейсы будут обнародованы в свое время, но, если Вам необходимо делать такие переводы уже сейчас, обратитесь в Ваш сервис технической поддержки Qt.

В X11 Qt также поддерживает Motif Drag & Drop Protocol. Реализация содержит некоторый код, который изначально написан Daniel Dardailler, и адаптирован для Qt Matt Koss <koss@napri.sk> и Trolltech. Вот оригинальное уведомление об авторских правах:

Copyright 1996 Daniel Dardailler.

Permission to use, copy, modify, distribute, and sell this software for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Daniel Dardailler not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. Daniel Dardailler makes no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty.

Modifications Copyright 1999 Matt Koss, under the same license as above.


Copyright © 2005 Trolltech Trademarks
Qt 4.1.0
Hosted by uCoz