Перетаскивание с помощью мыши (Drag&Drop) - интуитивно понятный жест, используемый во многих приложениях. Нельзя сказать, что он является наилучшим вариантом организации взаимодействия с программой. Но уметь встраивать подобное поведение в Qt-проекты не помешает.
Обычно в качестве элементов для перетаскивания рассматриваются текст и изображения. Мы создадим приложение, которое умеет перемещать и текст, и изображение одновременно. Оно будет состоять из двух виджетов.
Первый виджет позволит проводить загрузку изображнеия из файловой системы. Он же станет виджетом-источником, поддерживающим событие Drag
.
Второй виджет мы сделаем виджетом-приемником Drop
. Он сможет принимать то, что пользователь перетащит из первого виджета.
Вот как будет выглядеть наша программа:
автозапчасти от производителя.
Начнем с создания виджета, который умеет отображать изображение и подпись под ним.
Заголовочный файл imagetextwidget.h
:
#ifndef IMAGETEXTWIDGET_H
#define IMAGETEXTWIDGET_H
#include <QWidget>
class QLabel;
class ImageTextWidget : public QWidget {
Q_OBJECT
public:
ImageTextWidget( QWidget *parent = 0 );
~ImageTextWidget();
void setPixmap( const QPixmap& pix );
QPixmap getPixmap() const;
void setText( const QString& text );
QString getText() const;
private:
QLabel* m_imageLabel;
QLabel* m_textLabel;
};
#endif // IMAGETEXTWIDGET_H
Реализация imagetextwidget.cpp
:
#include "imagetextwidget.h"
#include <QLabel>
#include <QVBoxLayout>
ImageTextWidget::ImageTextWidget( QWidget *parent ) : QWidget( parent ) {
QVBoxLayout* layout = new QVBoxLayout;
layout->addWidget( m_imageLabel = new QLabel( trUtf8( "<Место для изображения>" ) ), 1 );
layout->addWidget( m_textLabel = new QLabel( trUtf8( "<Место для подписи>" ) ) );
setLayout( layout );
m_imageLabel->setAlignment( Qt::AlignVCenter | Qt::AlignHCenter );
m_textLabel->setAlignment( Qt::AlignHCenter );
resize( 300, 300 );
}
ImageTextWidget::~ImageTextWidget() {
}
void ImageTextWidget::setPixmap( const QPixmap& pix ) {
m_imageLabel->setPixmap( pix );
}
QPixmap ImageTextWidget::getPixmap() const {
return m_imageLabel->pixmap() ? *m_imageLabel->pixmap() : QPixmap();
}
void ImageTextWidget::setText( const QString& text ) {
m_textLabel->setText( text );
}
QString ImageTextWidget::getText() const {
return m_textLabel->text();
}
В этом коде нет ничего особенного, поэтому останавливаться на нем мы не будем.
Виджет-источник, из которого можно перетаскивать текст и изображения, уже поинтереснее. Посмотрим на его заголовочный файл dragwidget.h
:
#ifndef DRAGWIDGET_H
#define DRAGWIDGET_H
#include <imagetextwidget.h>
class DragWidget : public ImageTextWidget {
Q_OBJECT
public:
explicit DragWidget( QWidget* parent = 0 );
protected:
void mousePressEvent( QMouseEvent* event );
void mouseMoveEvent( QMouseEvent* event );
private slots:
void onLoadImage();
private:
QPoint m_dragStart;
};
#endif // DRAGWIDGET_H
Вся суть заключается в отлавливании событий нажатия кнопки мыши mousePressEvent()
и перемещения курсора mouseMoveEvent()
. Также обратите внимание на поле типа QPoint
. В нем мы будем хранить позицию начала движения.
А вот и реализация dragwidget.cpp
:
#include "dragwidget.h"
#include <QDebug>
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QPushButton>
#include <QBoxLayout>
#include <QFileDialog>
#include <QFileInfo>
#include <QPainter>
#include <QApplication>
DragWidget::DragWidget( QWidget* parent ) : ImageTextWidget( parent ) {
QPushButton* btn = new QPushButton( trUtf8( "Загрузить изображение" ) );
layout()->addWidget( btn );
connect( btn, SIGNAL( clicked( bool ) ), SLOT( onLoadImage() ) );
}
void DragWidget::mousePressEvent( QMouseEvent* event ) {
m_dragStart = event->pos();
}
void DragWidget::mouseMoveEvent( QMouseEvent *event ) {
if(
( event->buttons() & Qt::LeftButton ) &&
!getPixmap().isNull() &&
QApplication::startDragDistance() <= ( event->pos() - m_dragStart ).manhattanLength()
) {
QDrag* drag = new QDrag( this );
QMimeData* mimeData = new QMimeData;
mimeData->setImageData( getPixmap().toImage() );
mimeData->setText( getText() );
drag->setMimeData( mimeData );
drag->setPixmap( getPixmap() );
Qt::DropAction result = drag->exec( Qt::MoveAction );
qDebug() << "Drop action result: " << result;
if( result == Qt::MoveAction ) {
setPixmap( QPixmap() );
setText( "" );
}
}
}
void DragWidget::onLoadImage() {
QString filePath = QFileDialog::getOpenFileName(
this,
trUtf8( "Загрузка изображения" ),
".",
trUtf8( "Изображения (*.jpg *.png *.gif)" )
);
if( !filePath.isEmpty() ) {
QPixmap pix( filePath );
setPixmap( pix );
QFileInfo info( filePath );
setText( info.fileName() );
}
}
В функции-члене mousePressEvent()
мы всего лишь сохраняем позицию курсора, в которой произошел щелчок. Главное действие происходит в mouseMoveEvent()
, которое соответствует перемещению мыши. Сначала мы проверяем, что перетаскивание имеет смысл, т.е. нажата левая кнопка мыши, имеется загруженное изображение и пройденное расстояние от начальной позиции превышает некий минимум, который мы узнаем с помощью QApplication::startDragDistance()
.
Если все хорошо, то можно создать экземпляр QDrag
. При этом информация для перетаскивания помещается в QMimeData
. Мы кладем в него изображение и текст (как и планировали). На самом деле, в QMimeData
можно передавать вообще все, что угодно. Но об этом в другой раз.
Следующим шагом мы связываем mimeData
с drag
. Кроме того, для drag
устанавливаем изображение, которое будет отображаться при перемещении.
Ключевой момент: QDrag
работает по принципу, похожему на модальные диалоговые окна, поэтому для его использования вызываем drag->exec()
. В качестве параметра передаем Qt::MoveAction
(есть и другие варианты, но их в этот раз трогать не будем).
Если виджет-приемник (которым мы сейчас займемся) успешно принял данные, то drag->exec()
вернет Qt::MoveAction
. Это означает, что перемещение прошло успешно, поэтому просто очищаем изображение и текст, которые здесь больше не нужны.
Реализация виджета-приемника проще, чем для виджета-источника. Убедимся в этом сами. И начнем, как обычно, с заголовочного файла dropwidget.h
:
#ifndef DROPWIDGET_H
#define DROPWIDGET_H
#include <imagetextwidget.h>
class DropWidget : public ImageTextWidget {
Q_OBJECT
public:
explicit DropWidget( QWidget* parent = 0 );
protected:
void dragEnterEvent( QDragEnterEvent* event );
void dropEvent( QDropEvent* event );
};
#endif // DROPWIDGET_H
Обработка вновь основана на событиях. Для этого мы переопределяем функции-члены dragEnterEvent()
и dropEvent()
. Посмотрим, что в них происходит (файл dropwidget.cpp
):
#include "dropwidget.h"
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>
DropWidget::DropWidget( QWidget* parent ) : ImageTextWidget( parent ) {
setAcceptDrops( true );
}
void DropWidget::dragEnterEvent( QDragEnterEvent* event ) {
QStringList formats = event->mimeData()->formats();
qDebug() << formats;
if( formats.contains( "application/x-qt-image") && formats.contains( "text/plain" ) ) {
event->acceptProposedAction();
}
}
void DropWidget::dropEvent( QDropEvent* event ) {
setPixmap( QPixmap::fromImage( event->mimeData()->imageData().value< QImage >() ) );
setText( event->mimeData()->text() );
event->acceptProposedAction();
}
Обработчик события dragEnterEvent()
срабатывает, когда курсор мыши, "заряженный" данными виджета-источника, оказывается над виджетом-приемником. Сначала мы выводим на консоль список форматов данных, которые содержит в себе событие.
Замечание: Когда запустите получившуюся программу, можете провести эксперимент и поперетаскивать что-нибудь из других приложений. Хоть наш виджет вряд ли сможет принять их содержимое, но зато выведет на консоль то, что они пытаются передать.
Однако главная задача dragEnterEvent()
заключается в том, чтобы решить, нужно ли принимать перетаскиваемое содержимое или нет. Проверка осуществляется с помощью функций event->mimeData()->hasImage()
и event->mimeData->hasText()
. Если проверка пройдена, то для подтверждения достаточно вызвать event->acceptProposedAction()
.
Обработчик события dropEvent()
вызывается в самый ответственный момент, т.е. когда перемещаемое вложение "приземляется" на виджет (в момент отпускания левой кнопки мыши над ним). В этом случае мы просто устанавливаем изображение и текст для отображения в виджете-приемнике. Кроме того, не забудьте про вызов event->acceptProposedAction()
. Таким образом мы сообщаем виджету-источнику, что успешно приняли его содержимое.
А теперь организуем запуск нашей программы. Файл main.cpp
:
#include "dragwidget.h"
#include "dropwidget.h"
#include <QApplication>
int main( int argc, char* argv[] ) {
QApplication a( argc, argv );
DragWidget dragWgt;
dragWgt.show();
DropWidget dropWgt;
dropWgt.show();
return a.exec();
}
Мы рассмотрели лишь самый минимум возможностей, которые предоставляет Qt для реализации обработчиков Drag&Drop. Но этого уже хватит для решения большинства стандартных задач.
Скачать исходники с примером использования Drag&Drop в Qt
Пожалуйста =)
Anonymous
Супер! Здорово помогло!
Спасибо:)