文档章节

Qt 学习之路 2(52):使用拖放

SuShine
 SuShine
发布于 2016/05/12 23:10
字数 2213
阅读 93
收藏 4

拖放(Drag and Drop),通常会简称为 DnD,是现代软件开发中必不可少的一项技术。它提供了一种能够在应用程序内部甚至是应用程序之间进行信息交换的机制。操作系统与应用程序之间进行的剪贴板内容的交换,也可以被认为是拖放的一部分。

 

拖放其实是由两部分组成的:拖动和释放。拖动是将被拖放对象进行移动,释放是将被拖放对象放下。前者是一个按下鼠标按键并移动的过程,后者是一个松开鼠标按键的过程;通常这两个操作之间的鼠标按键是被一直按下的。当然,这只是一种普遍的情况,其它情况还是要看应用程序的具体实现。对于 Qt 而言,一个组件既可以作为被拖动对象进行拖动,也可以作为释放掉的目的地对象,或者二者都是。

在下面的例子中(来自 C++ GUI Programming with Qt4, 2nd Edition),我们将创建一个程序,将操作系统中的文本文件拖进来,然后在窗口中读取内容。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

class MainWindow : public QMainWindow

{

    Q_OBJECT

public:

    MainWindow(QWidget *parent = 0);

    ~MainWindow();

protected:

    void dragEnterEvent(QDragEnterEvent *event);

    void dropEvent(QDropEvent *event);

private:

    bool readFile(const QString &fileName);

    QTextEdit *textEdit;

};

注意到我们需要重写dragEnterEvent()dropEvent()两个函数。顾名思义,前者是拖放进入的事件,后者是释放鼠标的事件。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

{

    textEdit = new QTextEdit;

    setCentralWidget(textEdit);

 

    textEdit->setAcceptDrops(false);

    setAcceptDrops(true);

 

    setWindowTitle(tr("Text Editor"));

}

 

MainWindow::~MainWindow()

{

}

在构造函数中,我们创建了QTextEdit的对象。默认情况下,QTextEdit可以接受从其它应用程序拖放过来的文本类型的数据。如果用户把一个文件拖到这面,默认会把文件名插入到光标位置。但是我们希望让MainWindow读取文件内容,而不是仅仅插入文件名,所以我们在MainWindow中加入了拖放操作。首先要把QTextEditsetAcceptDrops()函数置为 false,并且把MainWindowsetAcceptDrops()置为 true,这样我们就能够让MainWindow截获拖放事件,而不是交给QTextEdit处理。

 

1

2

3

4

5

6

void MainWindow::dragEnterEvent(QDragEnterEvent *event)

{

    if (event->mimeData()->hasFormat("text/uri-list")) {

        event->acceptProposedAction();

    }

}

当用户将对象拖动到组件上面时,系统会回调dragEnterEvent()函数。如果我们在事件处理代码中调用acceptProposeAction()函数,就可以向用户暗示,你可以将拖动的对象放在这个组件上。默认情况下,组件是不会接受拖放的。如果我们调用了这个函数,那么 Qt 会自动以光标样式的变化来提示用户是否可以将对象放在组件上。在这里,我们希望告诉用户,窗口可以接受拖放,但是我们仅接受某一种类型的文件,而不是全部文件。我们首先检查拖放文件的 MIME 类型信息。MIME 类型由 Internet Assigned Numbers Authority (IANA) 定义,Qt 的拖放事件使用 MIME 类型来判断拖放对象的类型。关于 MIME 类型的详细信息,请参考 http://www.iana.org/assignments/media-types/。MIME 类型为 text/uri-list 通常用来描述一个 URI 列表。这些 URI 可以是文件名,可以是 URL 或者其它的资源描述符。如果发现用户拖放的是一个 text/uri-list 数据(即文件名),我们便接受这个动作。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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, tr("Drag File")));

    }

}

 

bool MainWindow::readFile(const QString &fileName)

{

    bool r = false;

    QFile file(fileName);

    QString content;

    if(file.open(QIODevice::ReadOnly)) {

        content = file.readAll();

        r = true;

    }

    textEdit->setText(content);

    return r;

}

当用户将对象释放到组件上面时,系统回调dropEvent()函数。我们使用QMimeData::urls()来获得QUrl的一个列表。通常,这种拖动应该只有一个文件,但是也不排除多个文件一起拖动。因此我们需要检查这个列表是否为空,如果不为空,则取出第一个,否则立即返回。最后我们调用readFile()函数读取文件内容。这个函数的内容很简单,我们前面也讲解过有关文件的操作,这里不再赘述。现在可以运行下看看效果了。

接下来的例子也是来自 C++ GUI Programming with Qt4, 2nd Edition。在这个例子中,我们将创建左右两个并列的列表,可以实现二者之间数据的相互拖动。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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;

};

ProjectListWidget是我们的列表的实现。这个类继承自QListWidget。在最终的程序中,将会是两个ProjectListWidget的并列。

 

1

2

3

4

5

ProjectListWidget::ProjectListWidget(QWidget *parent)

    : QListWidget(parent)

{

    setAcceptDrops(true);

}

构造函数我们设置了setAcceptDrops(),使ProjectListWidget能够支持拖动操作。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

void ProjectListWidget::mousePressEvent(QMouseEvent *event)

{

    if (event->button() == Qt::LeftButton)

        startPos = event->pos();

    QListWidget::mousePressEvent(event);

}

 

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;

    }

}

mousePressEvent()函数中,我们检测鼠标左键点击,如果是的话就记录下当前位置。需要注意的是,这个函数最后需要调用系统自带的处理函数,以便实现通常的那种操作。这在一些重写事件的函数中都是需要注意的,前面我们已经反复强调过这一点。

mouseMoveEvent()函数判断了,如果鼠标在移动的时候一直按住左键(也就是 if 里面的内容),那么就计算一个manhattanLength()值。从字面上翻译,这是个“曼哈顿长度”。首先来看看event.pos() - startPos是什么。在mousePressEvent()函数中,我们将鼠标按下的坐标记录为 startPos,而event.pos()则是鼠标当前的坐标:一个点减去另外一个点,这就是一个位移向量。所谓曼哈顿距离就是两点之间的距离(按照勾股定理进行计算而来),也就是这个向量的长度。然后继续判断,如果大于QApplication::startDragDistance(),我们才进行释放的操作。当然,最后还是要调用系统默认的鼠标拖动函数。这一判断的意义在于,防止用户因为手的抖动等因素造成的鼠标拖动。用户必须将鼠标拖动一段距离之后,我们才认为他是希望进行拖动操作,而这一距离就是QApplication::startDragDistance()提供的,这个值通常是 4px。

performDrag()开始处理拖放的过程。这里,我们要创建一个QDrag对象,将 this 作为 parent。QDrag使用QMimeData存储数据。例如我们使用QMimeData::setText()函数将一个字符串存储为 text/plain 类型的数据。QMimeData提供了很多函数,用于存储诸如 URL、颜色等类型的数据。使用QDrag::setPixmap()则可以设置拖动发生时鼠标的样式。QDrag::exec()会阻塞拖动的操作,直到用户完成操作或者取消操作。它接受不同类型的动作作为参数,返回值是真正执行的动作。这些动作的类型一般为Qt::CopyActionQt::MoveActionQt::LinkAction。返回值会有这几种动作,同时还会有一个Qt::IgnoreAction用于表示用户取消了拖放。这些动作取决于拖放源对象允许的类型,目的对象接受的类型以及拖放时按下的键盘按键。在exec()调用之后,Qt 会在拖放对象不需要的时候释放掉。

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)

{

    ProjectListWidget *source =

            qobject_cast(event->source());

    if (source && source != this) {

        event->setDropAction(Qt::MoveAction);

        event->accept();

    }

}

 

void ProjectListWidget::dropEvent(QDropEvent *event)

{

    ProjectListWidget *source =

            qobject_cast(event->source());

    if (source && source != this) {

        addItem(event->mimeData()->text());

        event->setDropAction(Qt::MoveAction);

        event->accept();

    }

}

dragMoveEvent()dropEvent()相似。首先判断事件的来源(source),由于我们是两个ProjectListWidget之间相互拖动,所以来源应该是ProjectListWidget类型的(当然,这个 source 不能是自己,所以我们还得判断source != this)。dragMoveEvent()中我们检查的是被拖动的对象;dropEvent()中我们检查的是释放的对象:这二者是不同的。

本文转载自:http://www.devbean.net/2013/05/qt-study-road-2-dnd/

共有 人打赏支持
SuShine
粉丝 123
博文 504
码字总数 148653
作品 0
朝阳
后端工程师
QT学习探索之路

近期由于工作需要在学习QT做界面开发,再次将我最近一个月的学习心得记录,同时欢迎各位大牛给我提出学习建议。 1:我的学习方法: 第一阶段: 主要是参考 C++ GUI Qt4 编程这本书 主要学习Q...

wb999999abc
2017/05/20
0
0
Python GUI教程(十二):使用拖放控件

文章首发个人博客:http://zmister.com/archives/186.html 微信公众号:州的先生 同步更新 Pytho爬虫、数据分析、机器学习、渗透测试、Web开发:http://zmister.com/ 在之前的文章中,构建了...

州的先生
2017/11/27
0
0
Qt 国际化及 Qt Linguist 的使用

前几天,偶然间看到光哥的一篇代码,里面有这样一句话: action->setText(tr("开始")); 并且在main函数中还有这种一段代码: int main(int argc, char *argv[]){ } devbean大神和dbzhang800大...

鉴客
2014/03/06
1K
0
收藏的博客 -- Qt/C++学习

Qt下载官网 http://download.qt.io/ Qt知名社区 http://www.qtcn.org/bbs/i.php http://www.qter.org/ Qt知名博客 https://www.devbean.net/ --- Qt学习之路,Qt Creator源码学习 http://blo......

libaineu2004
2017/08/18
0
0
鼠标拖放技术二

接着上次的说,上次说到了拖放技术,今天依然是一个例子,同样是来自《C++ GUI Programming with Qt 4, 2nd Edition》的。 这次的 demo 还算是比较实用:实现的是两个 list 之间的数据互拖。...

晨曦之光
2012/04/13
420
0

没有更多内容

加载失败,请刷新页面

加载更多

防止快速重复点击的两种思维

防止重复执行的两种思维 场景 下单时,提交按钮,因为网络卡顿或者手快重复点击,导致重复提交订单; 微博,更新个人状态或发表评论时,快速多次点击[发送]按钮,导致相同的信息发送多次. 解决思路 ...

黄威
34分钟前
0
0
在windows环境下使用Virtualbox虚拟Debian系统来运行Docker

标题绕口。 我之前一直使用 Virtualbox 和 homestead 来运行我的 PHP 开发环境。最近决心开始尝试 DevOps,使得开发、部署容器化,来化解人为操作失误和环境不兼容等问题造成的各种损失。就打...

zgldh
35分钟前
0
0
python map()

map()函数 map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。(利用生成器的原理,并不马上返回值,...

南桥北木
49分钟前
0
0
分享几个 SpringBoot 实用的小技巧

前言 最近分享的一些源码、框架设计的东西。我发现大家热情不是特别高,想想大多数应该还是正儿八经写代码的居多;这次就分享一点接地气的: SpringBoot 使用中的一些小技巧。 算不上多高大上...

Java干货分享
50分钟前
2
0
day123-20181021-英语流利阅读-待学习

这款新字体,比记忆面包还管用 Lala 2018-10-21 1.今日导读 字体能跟学习效果有什么关系?你还别说,来自澳洲的心理学家和设计师们,还真创造了一款号称能够帮助大家记忆信息、增强学习效果的...

飞鱼说编程
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部