拖放,拖动和释放(Drag and Drop),允许用户利用鼠标在不同组件之间或一个组件上进行数据的拖动。如果哪个组件支持拖放操作,就在该组件的设计上实现某些必要的函数。比如说,我们可能需要在MainWindow这个主窗口上实现拖放,那就在这个类中实现一些函数。如果我们自定义了一个组件,比如说定义了一个类MyDnDWidget继承自QWidget,我们就在MyDndWidget类中实现特定的函数来支持拖放。
Drag and Drop Types
QMimeData类是Qt内部的一个容器,它记录着大量拥有Mime Type(元类型)的数据。可以把它当成是一个map,每一个元素都是”Mime Type, Mime Data”这样的键值对,但是使用的时候不要像map一样使用,只是用于理解。
拖放操作不只局限在文本和图像上,任何类型的信息在拖放操作中都能被转换。为了在应用之间拖动信息,这些应用彼此都应该知道他们允许接收哪种数据格式,同时也应该知道他们可以产生哪种数据格式。这个是由MIME Types实现的。
被构造的QDrag对象包含MIME Types的列表,它能使用这些去表现数据,同时接收拖动数据的那个窗体使用这些MIME Types中的一个去访问数据。Mime Type可以是Qt中提供的,也可以是用户自定义的,如果需要自定义一个Mime Type,可以重新实现mimeTypes()函数:
mimeTypes
QStringList QAbstractItemModel::mimeTypes() const
这个函数返回允许的Mime Types列表。默认情况下,内置的models和views使用内部的Mime Type,比如text/uri-list。
当重新实现一个支持拖放操作的model时,如果设计者想要返回的数据格式不同于内部的Mime Type,那就需要重新实现这个函数来返回自定义的Mime Types列表。通常的实现形式为:
QStringList types;
types << /* the custom mime types */;
return types;
如果在自定义model中重新实现了这个函数,那么同时也需要重新实现mimeData()和dropMimeData()函数:
mimeData
QMimeData *QAbstractItemModel::mimeData(const QModelIndexList &indexes) const
这个函数返回一个QMimeData *类型的对象,这个对象包含序列化的数据元素,而那些数据元素存储在形参indexes中。换句话说,就是将模型索引列表indexes中的数据序列化,然后存储在一个QMimeData中,最后返回这个QMimeData指针。序列化的方法是使用QByteArray和QDataStream两个类。通常的实现格式为:
if(indexes.isEmpty())
return 0;
QMimeData *mimeData = new QMimeData;
QByteArray encodeData;
QDataStream stream(&encodeData, QIODevice::WriteOnly);
foreach(QModelIndex index, indexes)
{
if(index.value())
{
/* 将index中存储的数据取出来 */
stream << /* 数据1 */ << /* 数据2 */ << /* ... */;
}
}
mimeData->setData(/* your custom mime types */, encodeData);
return mimeData;
需要注意的是,当模型索引列表indexes为空时,返回0;
dropMimeData
bool QAbstractItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
当Qt检测到有Mime Data被释放到以该模型为model的组件上时,相应model的这个函数被调用。设计者需要重新实现这个函数来处理被释放的数据,通常是将数据存储到(插入或追加到)model的数据结构中。如果这个释放动作和数据被model处理则返回true,否则返回false;
参数中的row, column, parent指明数据释放的位置。如果row和column为-1,意味着被释放的数据释放到了parent上,通常是追加到parent的孩子中。如果row和column大于等于0,则被插入到parent孩子的特定位置。
通常的实现格式为:
if(!data->hasFormats(/* the custom mime types */)
return false;
if(action == Qt::IgnoreAction)
return true;
/* 确定插入的位置position */
QByteArray encodeData = data->data(/* the custom mime types */);
QDataStream stream(&encodeData, Qt::ReadOnly);
while(!stream.atEnd())
{
stream >> /* 数据1 */ >> /* 数据2 */ >> /* ... */;
beginInsertRows(parent, position, position);
/* model内部数据结构执行插入操作 */
endInsertRows();
++position;
}
return true;
Drop Action
在拖放模型中,用户既可以拖动信息的拷贝(复制)也可以拖动信息本身(剪切)到一个新地方。但是直到拖放操作完成之后程序才知道用户是想剪切还是想拷贝。这在拖动不同组件之间的信息时没有什么影响,但是在同一个组件上拖动时,检查使用了哪种drop action是很重要的。
Qt::DropAction
Qt::MoveAction 移动数据本身(剪切)
Qt::CopyAction 复制数据(复制)
Qt::IgnoreAction 忽略数据
在释放数据的时候,Qt会调用鼠标所在位置上组件的dropEvent()函数,设计者需要在这个函数中告诉拖放操作的数据源drop action以便数据源确定是复制还是剪切。
dropEvent
void QWidget::dropEvent(QDropEvent *event)
通知数据源drop action
event->setDropAction(Qt::MoveAction); /* 通知数据源删除原数据 */
event->accept();
或者
event->setDropAction(Qt::CopyAction); /* 通知数据源保留原数据 */
event->accept();
通常拖动操作都是开始于鼠标点击(mousePressEvent()函数),而每个拖动操作都是一个QDrag类,所以在鼠标点击函数中需要定义一个QDrag类,然后执行拖动操作,等待释放动作的反馈。可以把这个函数看成数据源。
mousePressEvent
void QWidget::mousePressEvent(QMouseEvent *event)
{
/* ... */
/* 取出点击位置的数据,进行序列化 */
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
if(dropAction == Qt::CopyAction || dropAction == IgnoreAction)
{
/* 不需要删除原数据 */
}
else if(dropAction == Qt::MoveAction)
{
/* 删除原数据 */
update(/* event->pos()处的范围矩形 */)
}
}
其他一些需要实现的函数
下面这些类处理拖放和必要的mime type的编码和解码:
QDrag:为以Mime Data为基础的拖放转化提供支持。
QDragEnterEvent:这个类型的事件被发送到窗口如果有数据被拖进这个窗口。
QDragLeaveEvent:这个类型的事件被发送到窗口如果有数据被拖离这个窗口。
QDragMoveEvent:当正在拖动时,这个类型的事件会一直被发出。
QDropEvent:当拖放操作完成时,这个类型的时候被发出。
在实现拖放操作时就是围绕这几个类来实现特定的函数。
dragEnterEvent
void QWidget::dragEnterEvent(QDragEnterEvent *event);
protected作用域的函数。当鼠标拖进一个窗体的时候,该窗体的这个函数被调用,传递的事件作为参数。如果这个事件被忽略了(ignored),那么这个窗体不会接收任何拖动事件。换句话说这个函数就是用于提醒Qt该组件接收的数据类型,判断是accept()还是ignore()这个事件。通常的实现格式为:
if(event->mimeData()->hasFormats(/* the custom mime types */) event->accept(); else event->ignore();
dragLeaveEvent
void QWidget::dragLeaveEvent(QDragLeaveEvent *event);
顾名思义,这个函数就是当拖动超出该组件范围时调用的。
dragMoveEvent
void QWidget::dragMoveEvent(QDragMoveEvent *event);
当正在拖动时,会一直调用这个函数。可以改变一些变量比如记录此时的投影矩形,然后在绘制函数中绘制。
paintEvent
void QWidget::paintEvent(QPaintEvent *event);
这个函数就是用于绘制所在类的。它可以被调用由以下理由:
1.repaint()或者update()函数被调用;
2.窗口部件被遮挡并且已经被Qt发现;
3.其他的理由
绘制的时候使用画刷类QPainter
QPainter painter;
painter.begin(this);
/* 绘制代码 */
painter.end();