Обеспечение поддержки технологии «drag-and-drop»

Обеспечение поддержки технологии «drag-and-drop»

Технология «drag-and-drop» состоит из двух действий: перетаскивание «захваченных» объектов и их «освобождение». Виджеты в Qt могут использоваться в качестве переносимых объектов, в качестве места отпускания этих объектов или в обоих качествах.

В нашем первом примере мы показываем, как приложение Qt принимает объект, перенесенный из другого приложения. Приложение Qt представляет собой главное окно, использующее текстовый редактор QTextEdit в качестве центрального виджета. Когда пользователь переносит текстовый файл с рабочего стола компьютера или из проводника файловой системы и оставляет его в окне этого приложения, оно загружает файл в QTextEdit.

Ниже приводится пример определения класса MainWindow:

01 class MainWindow : public QMainWindow

02 {

03 Q_OBJECT

04 public:

05 MainWindow();

06 protected:

07 void dragEnterEvent(QDragEnterEvent *event);

08 void dropEvent(QDropEvent *event);

09 private:

10 bool readFile(const QString &fileName);

11 QTextEdit *textEdit;

12 }

Класс MainWindow переопределяет функции dragEnterEvent() и dropEvent() класса QWidget. Поскольку целью примера является демонстрация механизма «drag-and-drop», большая часть функциональности класса главного окна здесь не рассматривается.

01 MainWindow::MainWindow()

02 {

03 textEdit = new QTextEdit;

04 setCentralWidget(textEdit);

05 textEdit->setAcceptDrops(false);

06 setAcceptDrops(true);

07 setWindowTitle(tr("Text Editor"));

08 }

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

01 voidMainWindow::dragEnterEvent(QDragEnterEvent *event)

02 {

03 if (event->mimeData()->hasFormat("text/uri-list"))

04 event->acceptProposedAction();

05 }

Функция dragEnterEvent() вызывается всякий раз, когда пользователь переносит объект на какой-нибудь виджет. Если мы вызываем функцию acceptProposedAction() при обработке этого события, мы указываем, что пользователь может отпустить переносимый объект в данном виджете. По умолчанию виджет не смог бы принять переносимый объект. Qt автоматически изменяет форму курсора для уведомления пользователя о возможности или невозможности приема объекта виджетом.

Здесь мы хотим позволить пользователю переносить файлы, но не более того. Для этого мы проверяем MIME—тип переносимого объекта. MIME—тип text/uri-list используется для хранения списка универсальных идентификаторов ресурсов (URI — universal resource identifier), в качестве которых могут выступать имена файлов, адреса URL (например, адресные пути HTTP и FTP) или идентификаторы других глобальных ресурсов. Стандартные типы MIME определяются Агентством по выделению имен и уникальных параметров протоколов сети Интернет (Internet Assigned Numbers Authority — IANA). Они состоят из типа и подтипа, разделенных слешем. Типы MIME используются буфером обмена и механизмом «drag-and-drop» для идентификации различных типов данных. Официальный список MIME—типов доступен по адресу http://www.iana.org/assignments/media-types/.

01 void MainWindow::dropEvent(QDropEvent *event)

02 {

03 QList<QUrl> urls = event->mimeData()->urls();

04 if (urls.isEmpty())

05 return;

06 QString fileName = urls.first().toLocalFile();

07 if (fileName.isEmpty())

08 return;

09 if (readFile(fileName))

10 setWindowTitle(tr("%1 -%2").arg(fileName)

11 .arg(tr("Drag File")));

12 }

Функция dropEvent() вызывается, когда пользователь отпускает объект на виджете. Мы вызываем функцию QMimeData::urls() для получения списка адресов QUrl. Обычно пользователи переносят одновременно только один файл, но возможен также перенос сразу нескольких выделенных файлов. Если имеется несколько URL или полученный URL оказывается нелокальным, мы немедленно возвращаем управление.

QWidget содержит также функции dragMoveEvent() и dragLeaveEvent(), но для большинства приложений не потребуется их переопределять.

Второй пример показывает, как следует инициировать перетаскивание объекта и принимать его после отпускания. Мы создадим подкласс QListWidget, который будет поддерживать механизм «drag-and-drop» и входить в приложение Project Chooser (составитель проектов), показанное на рис. 9.1.

Рис. 9.1. Приложение Project Chooser.

Приложение Project Chooser предоставляет пользователю два виджета со списками имен людей. Каждый список представляет проект. Пользователь может с помощью механизма «drag-and-drop» перевести человека из одного проекта в другой.

Программный код по обеспечению механизма «drag-and-drop» находится в подклассе QListWidget. Ниже приводится определение класса:

01 class ProjectListWidget : public QListWidget

02 {

03 Q_OBJECT

04 public:

05 ProjectListWidget(QWidget *parent= 0);

06 protected:

07 void mousePressEvent(QMouseEvent *event);

08 void mouseMoveEvent(QMouseEvent *event);

09 void dragEnterEvent(QDragEnterEvent *event);

10 void dragMoveEvent(QDragMoveEvent *event);

11 void dropEvent(QDropEvent *event);

12 private:

13 void startDrag();

14 QPoint startPos;

15 };

ProjectListWidget переопределяет пять обработчиков событий, которые объявлены в QWidget.

01 ProjectListWidget::ProjectListWidget(QWidget *parent)

02 : QListWidget(parent)

03 {

04 setAcceptDrops(true);

05 }

В конструкторе мы обеспечиваем возможность приема переносимого объекта в виджете со списком.

01 void ProjectListWidget::mousePressEvent(QMouseEvent *event)

02 {

03 if (event->button() == Qt::LeftButton)

04 startPos = event->pos();

05 QListWidget::mousePressEvent(event);

06 }

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

01 void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)

02 {

03 if (event->buttons() & Qt::LeftButton) {

04 int distance = (event->pos() - startPos).manhattanLength();

05 if (distance >= QApplication::startDragDistance())

06 startDrag();

07 }

08 QListWidget::mouseMoveEvent(event);

09 }

Действие, при котором пользователь перемещает курсор мышки и одновременно держит нажатой левую кнопку, мы рассматриваем как начало перетаскивания объекта. Мы вычисляем расстояние между текущей позицией мышки и позицией нажатия левой кнопки мышки. Если это расстояние превышает рекомендованное в QApplication расстояние для регистрации начала перетаскивания (обычно 4 пикселя), мы вызываем закрытую функцию startDrag() для запуска процесса перетаскивания объекта. Это предотвращает инициирование процесса перетаскивания из-за дрожания руки пользователя.

01 void ProjectListWidget::startDrag()

02 {

03 QListWidgetItem *item = currentItem();

04 if (item) {

05 QMimeData *mimeData = new QMimeData;

06 mimeData->setText(item->text());

07 QDrag *drag = new QDrag(this);

08 drag->setMimeData(mimeData);

09 drag->setPixmap(QPixmap(":/images/реrson.png"));

10 if (drag->start(Qt::MoveAction) == Qt::MoveAction)

11 delete item;

12 }

13 }

В функции startDrag() мы создаем объект типа QDrag с указанием this в качестве родительского элемента. Объект QDrag хранит данные в объекте QMimeData. В нашем примере мы обеспечиваем данные типа text/plain, используя функцию QMimeData::setText(). Класс QMimeData содержит несколько функций, предназначенных для обработки наиболее распространенных типрв объектов переноса (изображений, адресов URL, цветов и т.д.); он может обрабатывать произвольные типы MIME, представленные массивами QByteArray. Вызов QDrag::setPixmap() задает пиктограмму, которая следует за курсором в процессе перетаскивания объекта.

Вызов функции QDrag::start() запускает операцию перетаскивания объекта и ждет, пока пользователь не отпустит перетаскиваемый объект или не отменит перетаскивание. В аргументе этой функции задается перечень поддерживаемых «операций перетаскивания» (Qt::CopyAction, Qt::MoveAction и Qt::LinkAction); она возвращает ту операцию перетаскивания, которая была выполнена (или Qt::IgnoreAction, если не было выполнено никакой операции). Тип выполняемой операции зависит от того, какие операции допускаются исходным виджетом, какие операции поддерживает целевой виджет и какие клавиши—модификаторы нажаты в момент отпуска переносимого объекта. После вызова этой функции Qt становится владельцем переносимого объекта и удалит его, когда он станет ненужным.

01 void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)

02 {

03 ProjectListWidget *source =

04 qobject_cast<ProjectListWidget *>(event->source());

05 if (source && source != this) {

06 event->setDropAction(Qt::MoveAction);

07 event->accept();

08 }

09 }

Виджет ProjectListWidget не только инициирует перенос объектов, он также является местом приема таких объектов, если они приходят от другого виджета ProjectListWidget того же самого приложения. QDragEnterEvent::source() возвращает указатель на виджет, который инициирует перенос, если этот виджет принадлежит тому же самому приложению; в противном случае он возвращает нулевой указатель. Мы используем qobject_cast<T>(), чтобы убедиться в инициировании переноса виджетом ProjectListWidget. Если все верно, мы указываем Qt на нашу готовность восприятия данного действия как переноса.

01 void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)

02 {

03 ProjectListWidget *source =

04 qobject_cast<ProjectListWidget *>(event->source());

05 if (source && source != this) {

07 event->setDropAction(Qt::MoveAction);

08 event->accept();

09 }

10 }

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

01 void ProjectListWidget::dropEvent(QDropEvent *event)

02 {

03 ProjectListWidget *source =

04 qobject_cast<ProjectListWidget *>(event->source());

05 if (source && source != this) {

06 addItem(event->mimeData()->text());

07 event->setDropAction(Qt::MoveAction);

08 event->accept();

09 }

10 }

В DropEvent() мы используем функцию QMimeData::text() для получения перенесенного текста и создаем элемент с этим текстом. Нам также необходимо воспринять данное событие как «операцию перетаскивания», чтобы указать исходному виджету на то, что он может теперь удалить первоначальную версию перенесенного элемента.

«Drag-and-drop» — мощный механизм передачи данных между приложениями. Однако в некоторых случаях его можно реализовать, не используя предусмотренные в Qt средства механизма «drag-and-drop». Если нам требуется переносить данные внутри одного виджета некоторого приложения, во многих случаях мы можем просто переопределить функции mousePressEvent() и mouseReleaseEvent().