拖放是Qt实现的应用程序内或者多个应用程序之间传递信息的一种直观的现代操作方式。有没有想到windows的剪贴板?数据的移动和复制功能都异曲同工嘞~
一、使拖放生效
拖放包含两个动作:拖动 和 放下。Qt窗口部件可以作为拖动点(drag site)、放下点(drop site)或者同时作为拖动和放下点使用。
第一个例子用来说明如何让一个Qt应用程序接受另一个程序触发的拖动事件。该Qt应用程序是一个QTextEdit为中央控件的主窗口。当用户从桌面或者一个文件浏览器中拖动一个文本文件到Qt程序时松开,程序把文件显示在QTextEdit控件中。
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
private:
bool readFile(const QString &fileName);
QTextEdit *textEdit;
};
在MainWindow类中,重新实现了
QWidget的函数dragEnterEvent()和dropEvent()。由于这个例子主要用来显示托拽,主窗口的很多其他功能都省略了。
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->setAcceptDrops(false);
setAcceptDrops(true);
setWindowTitle(tr("Text Editor"));
}
在构造函数中,我们创建了一个QTextEdit控件,并设置为中央控件。缺省情况下,QTextEdit接受来自其他应用程序拖拽来的文本,把文件名显示出来。由于drop事件是由子控件向父控件传播的,通过禁止QTextEdit控件的drop事件,来允许主窗口得到drop事件,我们就得到了MainWindow中的整个窗口的drop事件。
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("text/uri-list"))
event->acceptProposedAction();
}
剪贴板功能 都是以QMimeData类为基础的,而QMineData是一个可以提供不同数据格式的类。
任何时候用户拖动一个对象到一个控件上,函数dragEnterEvent()都会被调用。如果在这个事件处理函数中调用函数acceptProposeAction(),说明我们允许用户把这个对象拖拽到这个控件上。默认情况,控件不接收drag事件。Qt会自动改变光标状态指示用户当前的控件是否是一个合法的drop地点。
在这里我们只允许用户drag一个文本文件,因此,我们检查这个这个drag的MIME类型。MIME类型text/uri-list用来保存URL的一个地址列表,可以是文件名,URL(HTTP或者FTP路径),也可以是其他的全局资源标识。标准的MIME类型由IANA(Internet Assigned Numbers Authority)定义,由一个类型名/子类型名组成。MIME类型用于在剪贴板和拖拽使用时区别不同的数据类型,MIME类型列表可以点击访问http://www.iana.org/assignments/media-types/得到。
void MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty())
return;
QString fileName = urls.first().toLocalFile();
if (fileName.isEmpty())
return;
if (readFile(fileName))
setWindowTitle(tr("%1 - %2").arg(fileName)
.arg(tr("Drag File")));
}
当用户将一个对象放在控件上drop时,函数dropEvent()就会调用。
QMineData::urls()得到一个QUrls列表。通常用户一次只会拖动一个文件,但是拖动多个文件也是允许的。如果用户拖动了多个URLs,或者URL不是一个文件名,程序立即返回。
QWidget还提供了dragMoveEvent()和dragLeaveEvent(),但是对于大多数应用程序并不需要重新实现它们。
第二个例子来说明怎样进行drag,怎样接受drop。我们将会创建一个QListWidget子类,它可以接受drag和drop。并把它作为Project Choonser 程序的一个组件。
效果如上,可以用按钮实现列表内容的移动,也可以用鼠标拖拽移动里面的内容。
类的定义如下:
#ifndef PROJECTLISTWIDGET_H
#define PROJECTLISTWIDGET_H
#include <QListWidget>
class ProjectListWidget : public QListWidget
{
Q_OBJECT
public:
ProjectListWidget(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
private:
void performDrag();
QPoint startPos;
};
#endif
重新定义了QWidget中定义的5个事件处理器。
类的实现:
#include <QtGui>
#include <QApplication>
#include "projectlistwidget.h"
ProjectListWidget::ProjectListWidget(QWidget *parent)
: QListWidget(parent)
{
setAcceptDrops(true);
}
构造函数中,使得列表框上的放下动作生效。
void ProjectListWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
startPos = event->pos();
QListWidget::mousePressEvent(event);
}
当用户按下鼠标左键,就把鼠标位置保存在startPos中。
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
performDrag();
}
QListWidget::mouseMoveEvent(event);
}
当用户按住鼠标左键并移动鼠标光标时,就认为是一个拖动的开始。我们计算当前鼠标位置和原来鼠标左键按下的点之间的距离(曼哈顿长度)从而来避免因为手握鼠标产生的抖动而产生拖动。
void ProjectListWidget::performDrag()
{
QListWidgetItem *item = currentItem();
if (item) {
QMimeData *mimeData = new QMimeData;
mimeData->setText(item->text());
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(QPixmap("images/person.png"));
if (drag->exec(Qt::MoveAction) == Qt::MoveAction)
delete item;
}
}
创建一个QDrag的对象,并且把This作为他的父对象,把数据存储在QMineData对象中。
QDrag::exec()调用启动并执行拖动操作,知道用户放下或取消此次拖动操作才会停止。
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source != this) {
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source != this) {
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void ProjectListWidget::dropEvent(QDropEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source != this) {
addItem(event->mimeData()->text());
event->setDropAction(Qt::MoveAction);
event->accept();
}
}