QT鼠标拖拽功能简介

来源:互联网 发布:董小飒淘宝月收入 编辑:程序博客网 时间:2024/05/10 14:24
Drag和Drop是两个完全不同的动作。Qt中的控件可以作为拖动(drag)的地点,也可以作为松开(drop)的地点,或者同时作为拖动和松开的地点。
第一个例子用来说明一个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();
}
 
任何时候用户拖动一个对象到一个控件上,函数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 程序的一个组件,如9.1所示:
Figure 9.1. The Project Chooser application
Project Chooser程序由两个名字列表控件组成。每一个列表控件表示一个项目。用户可以drag和drop列表中的名字,把一个名字从一个项目移到另一个项目中。
在列表控件的子类中实现了drag和drop部分的代码。下面是类的定义:
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 startDrag();
    QPoint startPos;
};
该类重新实现了5个QWidget中声明的事件处理函数。 
ProjectListWidget::ProjectListWidget(QWidget *parent)
    : QListWidget(parent)
{
    setAcceptDrops(true);
}
在构造函数中,我们让列表控件允许接受drop。
 
void ProjectListWidget::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        startPos = event->pos();
    QListWidget::mousePressEvent(event);
}
当用户点击了鼠标左键时,我们把鼠标位置保存在startPos变量中。然后调用基类QListWidget的mousePressEvent(),使QListWidget能处理鼠标点击事件,即列表控件的鼠标点击事件进入程序的消息循环。
 
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        int distance = (event->pos() - startPos).manhattanLength();
        if (distance >= QApplication::startDragDistance())
            startDrag();
    }
    QListWidget::mouseMoveEvent(event);
}
如果用户按住鼠标左键同时移动鼠标,把这个过程认为是一个drag。计算当前鼠标位置和鼠标第一次点击的位置之间的距离,如果这个距离大于QApplication认定的拖动的最小距离(通常为四个象素),调用私有函数startDrag()开始拖动。通过判断距离可以避免因为用户手抖动引起的误操作。
 
void ProjectListWidget::startDrag()
{
    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->start(Qt::MoveAction) == Qt::MoveAction)
            delete item;
    }
}
在startDrag()中,我们创建一个QDrag对象,this指针表示父类为当前的列表控件ProjectListWidget。QDrag对象保存了一个QMimeData对象中的数据。在这个例子中,我们只是使用QMimeData::setText()保存了一个text/plain 串。QMimeData提供了很多函数处理经常用到的拖动类型(如图像,URLs,颜色等等),对于用QByteArrays表示的任意MIME类型都可以处理。当drag发生时,函数QDrag::setPixmap()设置了跟随鼠标的图标。
调用QDrag::start()开始拖动,直到用户drop或者取消了拖动。函数的参数为多个“drag actions”的组合(Qt::CopyAction, Qt::MoveAction, Qt::LinkAction)。返回值为执行拖动的“drag action”,如果没有执行拖动的操作,则返回Qt::IgnoreAction。具体执行哪个action取决于源控件允许的操作,目标控件允许的操作和drop的同时是否有附加键按下。调用start()后,Qt拥有被拖动的对象,在不需要时将其删除。
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)
{
    ProjectListWidget *source =
            qobject_cast<ProjectListWidget *>(event->source());
    if (source && source != this) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
ProjectListWidget不但可以产生drag,还可以接受来自程序中其他ProjectListWidget控件的drag。如果drag发生在同一个应用程序中,QDragEnterEvent::source()得到产生drag控件的指针。如果不是一个程序,则返回空指针。qobject_cast<T>()可以确保拖动来自与一个ProjectListWidget控件。如果一切正常,则调用accept()通知Qt接受这个action作为一个移动操作
 
void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)
{
    ProjectListWidget *source =
            qobject_cast<ProjectListWidget *>(event->source());
    if (source && source != this) {
        event->setDropAction(Qt::MoveAction);
        event->accept();
    }
}
函数dragMoveEvent()和dragEnterEvent()的代码是相同的。这有必要,因为需要覆盖QListWidget的(实际上是QAbstractItemView的)函数实现。
 
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();
    }
}
在dropEvent()中,调用QMimeData::text()得到拖动的文本,并用这个文本创建一个列表项目。我们还需要调用event->setDropAction(Qt::MoveAction),用参数Qt::MoveAction通知源控件可以删除原来拖动的项目。
在程序间需要转移数据时,拖拽是一个非常有用的机制。但是有时候不用拖拽机制也可以实现拖拽同样的操作。如果我们只是想在同一个程序同一个控件中移动数据,只需要重写mousePressEvent()和mouseReleaseEvent()就可以。