1 背景
本文讲述了基于Qt实现文件拖放操作。拖放操作至少需要两个窗口,一个作为拖放源窗口,另一个作为拖放目标窗口。
2 实现
这里从QTreeView派生一个类TreeView,处理鼠标开始拖放操作及拖放事件。这个类既是拖放源窗口也是拖放目标窗口。
2.1 TreeView定义
TreeView定义如下:
class TreeView : public QTreeView
{
Q_OBJECT
public:
TreeView(QWidget *parent = nullptr);
signals:
void prepareDrag(QPoint const& point);
void dragEnter(QDragEnterEvent * event);
void dragMove(QDragMoveEvent * event);
void drop(QDropEvent * event);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent * event);
void dragMoveEvent(QDragMoveEvent * event);
void dropEvent(QDropEvent * event);
private:
QPoint pressPoint;
bool mousePress;
bool isDrag;
};
说明:
- 信号prepareDrag是开始拖放信号,这时TreeView是拖放源窗口。
- 信号dragEnter是TreeView做为拖放目标窗口,有拖放进入该窗口时发送的信号。
- 信号dragMove是TreeView做为拖放目标窗口,有拖放在该窗口移动时发送的信号。
- 信号drop是TreeView做为拖放目标窗口,有拖放在该窗口完成拖放时发送的信号。
- 重载函数mousePressEvent,mouseMoveEvent和mouseReleaseEvent处理鼠标事件,发送开拖放信号。
- 重载函数dragEnterEvent,dragMoveEvent和dropEvent处理拖放事件。
2.2 TreeView实现
2.2.1 构造函数
TreeView::TreeView(QWidget *parent)
: QTreeView(parent)
, mousePress(false)
, isDrag(false)
{
setMouseTracking(true);
setAcceptDrops(true);
}
说明:
- 通过setMouseTracking将鼠标设置为可跟踪的,以便处理鼠标移动操作。
- 通过setAcceptDrops将对象设置为拖放目标窗口。
2.2.2 鼠标事件
void TreeView::mousePressEvent(QMouseEvent *event)
{
if(event->button() == Qt::LeftButton)
{
pressPoint = event->pos();
mousePress = true;
}
QTreeView::mousePressEvent(event);
}
void TreeView::mouseMoveEvent(QMouseEvent *event)
{
if(mousePress)
{
int distance = (event->pos() - pressPoint).manhattanLength();
if(distance >= QApplication::startDragDistance())
{
mousePress = false;
emit prepareDrag(pressPoint);
}
isDrag = true;
}
if(isDrag)
return;
QTreeView::mouseMoveEvent(event);
}
void TreeView::mouseReleaseEvent(QMouseEvent *event)
{
mousePress = false;
isDrag = false;
QTreeView::mouseReleaseEvent(event);
}
说明:
- 在鼠标按下事件中,记下按下时鼠标位置pressPoint.
- 在鼠标移动事件中,根据移动距离判断是否是启动拖放操作。
- 是拖放操作设置isDrag为true。
- 在鼠标释放事件中,恢复状态。
2.2.3 拖放事件
void TreeView::dragEnterEvent(QDragEnterEvent * event)
{
emit dragEnter(event);
}
void TreeView::dragMoveEvent(QDragMoveEvent * event)
{
emit dragMove(event);
}
void TreeView::dropEvent(QDropEvent * event)
{
emit drop(event);
}
说明:拖放事件处理很简单,分别发送对应的信号。
3 使用
从QWidget派生一个类型Widget.
3.1 Widget 定义
Widget 定义如下:
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void beginDrag(QPoint const& point);
void dragEnter(QDragEnterEvent * event);
void dragMove(QDragMoveEvent * event);
void drop(QDropEvent * event);
private:
Ui::Widget *ui;
};
3.2 Widget实现
下面分别讲述一下构造函数,beginDrag,dragEnter,dragMove和drop的实现.
3.2.1 构造函数
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->treeview, SIGNAL(prepareDrag(QPoint)),
this, SLOT(beginDrag(QPoint)));
connect(ui->treeview, SIGNAL(dragEnter(QDragEnterEvent*)),
this, SLOT(dragEnter(QDragEnterEvent*)));
connect(ui->treeview, SIGNAL(dragMove(QDragMoveEvent*)),
this, SLOT(dragMove(QDragMoveEvent*)));
connect(ui->treeview, SIGNAL(drop(QDropEvent*)),
this, SLOT(drop(QDropEvent*)));
}
该函数将对象TreeView的4个信号与Widget对应的槽函数连接起来。
3.2.2 beginDrag
#define FILE_URL_HEADER "file:///"
void Widget::beginDrag(QPoint const& point)
{
QModelIndex index = ui->treeview->indexAt(point);
if(!index.isValid())
return;
QStringList fileNames;
if(index.row() % 2)
fileNames = QStringList() << FILE_URL_HEADER + "D:/file1.txt";
else
fileNames = QStringList() << FILE_URL_HEADER + "D:/file1.txt"
<< FILE_URL_HEADER + "D:/file2.txt";
QString text = fileNames.join("\n");
if(fileNames.size() > 1)
text += "\n";
QDrag *drag = new QDrag(ui->treeview);
QMimeData* mineData = new QMimeData();
mineData->setData("text/uri-list", text.toUtf8());
drag->setMimeData(mimeData);
drag->setPixmap(QPixmap(":/image/copy.png"));
drag->exec(Qt::LinkAction | Qt::MoveAction | Qt::CopyAction , Qt::CopyAction);
}
函数流程:
- 根据point获取鼠标是否选择对象,如果选中对象开始拖放,否则不拖放。
- 构造文件名数组,注意文件名以file:///为前缀。
- 以ui->treeview为参数创建拖放对象drag。
- 创建QMimeData对象mineData,通过setData将文件名设置给类型text/uri-list。
- 以mineData为参数来设置drag的MimeData。
- 设置drag的Pixmap作为拖放时图标显示。
- 开始进入拖放状态,默认拖放动作是Copy。同时也支持Link和Move拖放动作。
3.2.3 dragEnter
void Widget::dragEnter(QDragEnterEvent* event)
{
QMimeData const* mimeData = event->mimeData();
if(mimeData)
event->acceptProposedAction();
else
event->ignore()
}
该函数是拖放状态下鼠标进入窗口时调用。可以有如下选择:
- 择接受拖放请求,这时拖放图标显示为鼠标箭头+复制图标
- 不接受拖放请求调用,这时拖放图标显示为红色禁止+复制图标。
3.2.4 dragMove
void Widget::dragMove(QDropEvent* event)
{
QModelIndex index = ui->treeview->indexAt(event->pos());
bool isSelf = (event->source() == ui->treeview);
if(index.isValid())
{
if(isSelf && (index.row() % 2))
event->ignore();
else
event->acceptProposedAction();
}
else
{
if(isSelf)
event->ignore();
else
event->acceptProposedAction();
}
}
该函数是拖放状态下鼠标在窗口中移动时调用。可以根据实际应用需要选择接受还是拒绝拖放请求。
3.2.5 drop
void Widget::drop(QDropEvent* event)
{
QMimeData const* mimeData = event->mimeData();
if(!mimeData)
return;
QString text = mimeData->text().remove(FILE_URL_HEADER);
if(text.endsWith("\n"))
text.remove(text.size() - 1, 1);
QStringList fileNames = text.split("\n");
if(event->dropAction() == Qt::MoveAction)
{
// move files
}
else if(event->dropAction() == Qt::CopyAction)
{
// copy files
}
else if(event->dropAction() == Qt::LinkAction)
{
// create shortcuts
}
}
该函数是拖放状态下并且接受拖放请求,鼠标释放时调用函数。
函数流程:
- 从mimeData中获取文件名。
- 根据拖放动作执行对应的操作。
说明:默认是Copy动作,在拖放状态下按Alt或Alt+Shift键可以切换支持的拖放动作。
4 总结
本文讲述了Qt下文件拖放操作,其实不只是文件操作,其它操作也是可以,只需给给mineData设置对应对象就可以。另外本文描述文件拖放操作,只是自己程序内部拖放操作。与文件资源管理器相互拖放还需要给mineData设置其它格式对象。