拖放
拖放是在一个应用程序内或者多个应用程序之间传递信息的一种直观的现代操作方式。除了为剪贴板提供支持外,通常它还提供数据移动和复制的功能。
使拖放生效
拖放操作包括两个截然不同的动作:拖动和放下。Qt窗口部件可以作为拖动点(darg site) 、放下点(drop site) 或者同时作为拖动点和放下点。
Text Editor
Text Editor 介绍了如何让一个Qt应用程序接受由另一个应用程序执行的一个拖动操作。该Qt应用程序是一个以 QTextEdit 作为中央窗口部件的主窗口程序。当用户从桌面上或从文件资源管理器中拖动一个文本文件并且在这个应用程序上放下时,该应用程序就会将文本文件载入到 QTextEdit 中。
MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTextEdit>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
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;
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
MainWindow 类重新实现了来自 QWidget 的 dragEnterEvent()和dropEvent() 函数。由于该例子的主要目的是想说明拖放操作,所以一个主窗口类应该具有的很多功能在这里都被省略了。
MainWindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDragEnterEvent>
#include <QMimeData>
#include <QUrl>
#include <QList>
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->setAcceptDrops(false);
setAcceptDrops(true);
setWindowTitle(tr("Text Editor"));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if(event->mimeData()->hasFormat("text/uri-list"))
{
event->acceptProposedAction();
}
}
void MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls(); //get File name
if(urls.empty())
return;
QString fileName = urls.first().toLocalFile();
readFile(fileName);
}
bool MainWindow::readFile(const QString &filename)
{
QFile file(filename) ;
if(!file.open(QIODevice::ReadOnly|QIODevice::Text))
{
QMessageBox::warning(this,tr("Text Editor"),tr("Cant read file %1:%\n%2").arg(file.fileName()).arg(file.errorString()));
return false;
}
QTextStream in(&file);
QApplication::setOverrideCursor(Qt::WaitCursor);//设置鼠标等待
textEdit->setPlainText(in.readAll());
QApplication::restoreOverrideCursor();
}
MainWindow()
在构造函数中,创建了一个 QTextEdit 并且把它设置为中央窗口部件。默认情况下, QTextEdit 可以接受来自其他应用程序文本的拖动,并且如果用户在它上面放下一个文件,它将会把这个文件的名称插人到文本中。由于拖放事件是从子窗口部件传递给父窗口部件的,所以通过禁用 QTextEdit 上的放下操作以及启用主窗口上的放下操作,就可以在整个 MainWindow 窗口中获得放下事件。
dragEnterEvent()
当用户把一个对象拖动到这个窗口部件上时,就会调用 dragEnterEvent()。如果对这个事件调 acceptProposedAction(),就表明用户可以在这个窗口部件上拖放对象。默认情况下,窗口部件是不接受拖动的。 Qt 会自动改变光标来向用户说明这个窗口部件是不是有效的放点。
这里,我们希望用户拖动的只能是文件,而非其他类型的东西。为了实现这一点,我们可以检查拖动的 MIME 类型。 MIME 类型中的 text/uri-list 用于存储一系列的统一资源标识符(URI),它们可以是文件名、统一资源定位器 (URL,如HTTP或者 FTP 路径),或者其他全局资源标识符。标准的 MIME 类型是由国际因特网地址分配委员会 (IANA) 定义的,它们由类型、子类型信息以及分隔两者的斜线组成。MIME 类通常由剪贴板和拖放系统使用,以识别不同类型的数据。
dropEvent()
当用户在窗口都件上放下一个对象时,就会调用dropEvent()。我们调用函数 QMimeData::urls()来获得 QUrl 列表。通常,用户一次只拖动一个文件,但是通过拖动一个选择区域来同时拖动多个文件也是可能的。如果要拖放的 URL 不止一个,或者要拖放的 URL 不是一个本地文件名,则会立即返回到原调用处。
QWidget 也提供dragMoveEvent()和dragLeaveEvent()函数,但是在绝大多数应用程序中并不需要重新实现它们。
main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Project Chooser
如何初始化一个拖动并且接收一个放下。我们将创建一个支持拖放的QListWidget子类,并且把它作为Project Chooser应用程序的一个组件。
Project Chooser应用程序为用户提供了两个由姓名组成的列表框。两个列表框分别表示一个项目。用户可以在两个项目列表之间拖放这些姓名,将一个项目中的成员移到另一个项目中。
ProjectListWidget.h
#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
ProjectListWidget类重新实现了在QWidget中定义的5个事件处理器。
ProjectListWidget.cpp
#include <QtGui>
#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);
}
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::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();
}
}
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;
}
}
ProjectListWidget()
在构造函数中,我们使列表框上的放下生效。
mousePressEvent()
当用户按下鼠标左键,就把鼠标位置保存到startPos私有变量中。我们调用QListWidget中mousePressEvent()的实现,以确保QListWidget可以像平常一样有机会处理鼠标按下的事件。
mouseMoveEvent()
当用户按住鼠标左键并移动鼠标光标时,就认为这是一个拖动的开始,我们计算当前鼠标位置和原来鼠标左键按下的点之间的距离——这个"曼哈顿长度"其实是从坐标原点到该矢量长度快速计算的近似值。如果这个距离大于或等于QApplication推荐的拖动起始距离值(通常是4个像素)。那么就调用私有函数performDrag()以启动拖动操作。这可以避免用户因为手握鼠标抖动而产生拖动。
performDrag()
在performDrag() 中,创建了一个类型为QDrag的对象,并且把 this 作为它的父对象。这个QDrag对象将数据存储在 QMimeData 对象中。在这个实例中,我们利用 QMimeData::setText()提供了作为text/plain 字符串的数据。 MimeData 提供了一些可用于处理最常用拖放类型(诸如图像、 URL、颜色等等)的函数,同时也可以处理任意由 QByteArrays 表示的 MIME 类型。QDrag::setPixmap()调用则可以在拖放发生时使图标随光标移动。
QDrag::exec()调用启动并执行拖动操作直到用户放下或取消此次拖动操作才会停止。它把所有支持的"拖放动作"(如Qt::CopyAction, Qt::MoveAction 和 Qt::LinkAction()" 的组合作为其参数,并且返回被执行的拖放动你(如果没有执行任何动作,则返回Qt::IgnoreAction) 。至于执行的是哪个动作,取决于放下发生时源窗口部件是否允许、目标是否支持及按下了哪些组合键。在 exec() 调用后,Qt拥有拖动对象用所有权并且可以在不需要它的时候删除它。
dragEnterEvent()
ProjectListWidget 窗口部件不仅能发起拖动,还可以接收同 个应用程序中来自另外一个ProjectListWidget部件的拖动。如果窗口部件是同一个应用程序的一部分, QDragEnterEvent::source()返回一个启动这个拖动的窗口部件的指针;否则,返回一个空指针值。我们使用 qobject_cast<T>(),以确保这个拖动来自ProjectListWidget。如果一切无误,就告诉Qt预备将该动作认为是一个移动动作。
dragMoveEvent()
dragEnterEvent()中的代码与dragMoveEvent()中编写的代码基本相同。这样是必要的,因为需要重写 QListWidget 的函数实现(实际上是QAbstracItemView的函数实现)。
dropEvent()
dropEvent()中,我们使用 QMime::text() 重新找回拖动的文本并随文本创建一个拖动项。还需要将事件作为"移动动作"来接受,从而告诉源窗口部件现在可以删除原来的拖动项了。
拖放是在应用程序之间传递数据的有力机制。但是在某些情况下有可能在执行拖放时并未使用Qt的拖放工具。如果只是想在一个应用程序的窗口部件中移动数据,通常只要重新实现 mousePressEvent()和 mouseReleaseEvent() 函数就可以了。
ProjectDialog.h
#ifndef PROJECTDIALOG_H
#define PROJECTDIALOG_H
#include <QDialog>
#include "ui_projectdialog.h"
class ProjectDialog : public QDialog, private Ui::ProjectDialog
{
Q_OBJECT
public:
ProjectDialog(QWidget *parent = 0);
public slots:
void on_leftButton_clicked();
void on_rightButton_clicked();
private:
void moveCurrentItem(ProjectListWidget *source,
ProjectListWidget *target);
};
#endif
ProjectDialog.cpp
#include <QtGui>
#include "projectdialog.h"
ProjectDialog::ProjectDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
projectA->addItem("Giosue Carducci");
projectA->addItem("Eyvind Johnson");
projectA->addItem("Sally Prudhomme");
projectA->addItem("Henryk Sienkiewicz");
projectA->addItem("Carl Spitteler");
projectA->addItem("Rabindranath Tagore");
projectA->addItem("Kawabata Yasunari");
projectB->addItem("Rudolf Eucken");
projectB->addItem("Anatole France");
projectB->addItem("Rudyard Kipling");
projectB->addItem("Thomas Mann");
projectB->addItem("Eugene O'Neill");
projectB->addItem("Sigrid Undset");
}
void ProjectDialog::on_leftButton_clicked()
{
moveCurrentItem(projectB, projectA);
}
void ProjectDialog::on_rightButton_clicked()
{
moveCurrentItem(projectA, projectB);
}
void ProjectDialog::moveCurrentItem(ProjectListWidget *source,
ProjectListWidget *target)
{
if (source->currentItem()) {
QListWidgetItem *newItem = source->currentItem()->clone();
target->addItem(newItem);
target->setCurrentItem(newItem);
delete source->currentItem();
}
}
projectdialog.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ProjectDialog</class>
<widget class="QDialog" name="ProjectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>250</width>
<height>138</height>
</rect>
</property>
<property name="windowTitle">
<string>Choose Project</string>
</property>
<layout class="QGridLayout">
<item row="3" column="1">
<widget class="QToolButton" name="rightButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="projectchooser.qrc">
<normaloff>:/images/rightarrow.png</normaloff>:/images/rightarrow.png</iconset>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>21</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<widget class="QToolButton" name="leftButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="projectchooser.qrc">
<normaloff>:/images/leftarrow.png</normaloff>:/images/leftarrow.png</iconset>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="labelB">
<property name="text">
<string><html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Project B</span></p></body></html></string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelA">
<property name="text">
<string><html><head><meta name="qrichtext" content="1" /></head><body style=" white-space: pre-wrap; font-family:Sans Serif; font-weight:400; font-style:normal; text-decoration:none;"><p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Project A</span></p></body></html></string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item row="1" column="0" rowspan="4">
<widget class="ProjectListWidget" name="projectA"/>
</item>
<item row="1" column="2" rowspan="4">
<widget class="ProjectListWidget" name="projectB"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ProjectListWidget</class>
<extends>QListWidget</extends>
<header>projectlistwidget.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="projectchooser.qrc"/>
</resources>
<connections/>
</ui>
main.cpp
#include <QtGui>
#include "projectdialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
ProjectDialog dialog;
dialog.show();
return app.exec();
}