Draggable Icons Example例子展示了在同一个应用程序的窗体部件之间如何拖放图像数据。
(关于一些拖放的基本知识在我上一篇博客有介绍。)
例子程序功能是这样的,当我们在一个自定义widget(图中两个相同大小的框框就是自定义的widget)内拖动一个图片,会将图片位置改变。当我们将一个自顶一个widget中的图片拖动放置到另外一个中时,将会复制图到另外一边。
首先看main.cpp:
#include <QApplication>
#include <QHBoxLayout>
#include "dragwidget.h"
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(draggableicons);
QApplication app(argc, argv);
QWidget mainWidget;
QHBoxLayout *horizontalLayout = new QHBoxLayout;
horizontalLayout->addWidget(new DragWidget);
horizontalLayout->addWidget(new DragWidget);
mainWidget.setLayout(horizontalLayout);
mainWidget.setWindowTitle(QObject::tr("Draggable Icons"));
#ifdef Q_OS_SYMBIAN
mainWidget.showMaximized();
#else
mainWidget.show();
#endif
return app.exec();
}
Q_INIT_RESOURCE是一个初始化资源的函数,在一些平台,我们的资源文件.qrc需要存储在静态库,使用它将自动为我们导入资源文件,这就确保资源能够在启动时被初始化。
然后就是主程序类对象,然后实例化一个QWidget作为主窗口,再实例化两个自定义widget,用QHBoxLayout排版与呈现。#ifdef Q_OS_SYMBIAN ...可以不用管也可以去掉,这是用在塞班系统里的显示。
下面进入自定义的这个部件代码,看看它怎么样写的。
#include <QFrame>
QT_BEGIN_NAMESPACE
class QDragEnterEvent;
class QDropEvent;
QT_END_NAMESPACE
class DragWidget : public QFrame
{
public:
DragWidget(QWidget *parent=0);
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void mousePressEvent(QMouseEvent *event);
};
DragWidget是一个QFrame 的子类(QFrame是QWidget的子类,QFrame主要是添加了一个框)。DragWidget就是普通的重实现了拖放需要的事件响应函数。
DragWidget::DragWidget(QWidget *parent)
: QFrame(parent)
{
#if defined(Q_OS_SYMBIAN) || defined(Q_WS_MAEMO_5) || defined(Q_WS_SIMULATOR)
#else
setMinimumSize(200, 200);
#endif
setFrameStyle(QFrame::Sunken | QFrame::StyledPanel);
setAcceptDrops(true);
QLabel *boatIcon = new QLabel(this);
boatIcon->setPixmap(QPixmap(":/images/boat.png"));
boatIcon->move(10, 10);
boatIcon->show();
boatIcon->setAttribute(Qt::WA_DeleteOnClose);
QLabel *carIcon = new QLabel(this);
carIcon->setPixmap(QPixmap(":/images/car.png"));
carIcon->move(100, 10);
carIcon->show();
carIcon->setAttribute(Qt::WA_DeleteOnClose);
QLabel *houseIcon = new QLabel(this);
houseIcon->setPixmap(QPixmap(":/images/house.png"));
houseIcon->move(10, 80);
houseIcon->show();
houseIcon->setAttribute(Qt::WA_DeleteOnClose);
}
SunKen是QFrame::Shadow的一种,是下沉的3D投影效果。StyledPanel是QFrame::Shape的一种,定义框框的形状,即画一个矩形的框框,它可以呈现突出(Raised)效果和下沉(Sunken)效果。
为了使放置可行,setAcceptDrops(true)是必须的。
然后就是添加了三个label。这里我们可以看到,那些图片就是用的QLabel实例化的。
其中setAttribute(Qt::WA_DeleteOnClose);也是很常见的,它是让我们关闭程序时自动释放内存。
void DragWidget::mousePressEvent(QMouseEvent *event)
{
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (!child)
return;
QPixmap pixmap = *child->pixmap();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << pixmap << QPoint(event->pos() - child->pos());
QMimeData *mimeData = new QMimeData;
mimeData->setData("application/x-dnditemdata", itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
drag->setHotSpot(event->pos() - child->pos());
QPixmap tempPixmap = pixmap;
QPainter painter;
painter.begin(&tempPixmap);
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127));
painter.end();
child->setPixmap(tempPixmap);
if (drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction)
child->close();
else {
child->show();
child->setPixmap(pixmap);
}
}
鼠标按下事件,获取是否是点击到了图片(QLabel),没有则返回。
然后就是设置一个QDarg对象。用QDataStream形式将图片对象存储到QByteArray。再将这个数据存到QMimeData以供QDarg使用。
setHotSpot()设置热点,一般热点是左上角,这里点击下鼠标,这里不使用默认,而是设置在设置到我们鼠标点到的图片中的位置。
tempPixmap是干什么的?我们在获取的QLabel图像的位置覆盖画了一张原图片加灰的图片,这样来表示拖动时原图片的阴影效果。
在接下来就是开始执行拖动了。drag->exec() 如果接收到的返回值是Qt::MoveAction(dragEnterEvent, dragMoveEven, dropEvent的同一个对象内的拖动)视为移动,那么就关闭掉原先位置的图片。否则在新位置也显示一个。
void DragWidget::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
当拖动对象进入DragWidget时就会调用dragEnterEvent()。在这里面我们通常都是设置响应何种放置对象事件,对于这里的格式,"application/x-dnditemdata"是后面QMimeData::setData自定义设置的。必须与后面设置相同。
这里event->source() == this是判定是否在同一个对象,如果是同一个对象就是接收Qt::MoveAction,否则接收目的活动。
void DragWidget::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
对拖动移动事件也和上面响应相同。
void DragWidget::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
QByteArray itemData = event->mimeData()->data("application/x-dnditemdata");
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QPixmap pixmap;
QPoint offset;
dataStream >> pixmap >> offset;
QLabel *newIcon = new QLabel(this);
newIcon->setPixmap(pixmap);
newIcon->move(event->pos() - offset);
newIcon->show();
newIcon->setAttribute(Qt::WA_DeleteOnClose);
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
放置事件中,使用自定义放置对象。将QMimeData信息获取出来,相当于之前写入的逆过程。将创建放置的对象图片。