前言
通过一个例子学习 QTreeWidget 节点的拖拽功能。本示例仅实现树形结构的自我拖拽,用来更改上下级关系或者调整节点顺序。
子类化 QTreeWidget
- 概述
通过子类化 QTreeWidget 重新实现相关事件,以实现自定义功能。 - 头文件
#ifndef MYTREEWIDGET_H
#define MYTREEWIDGET_H
#include <QTreeWidget>
class MyTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit MyTreeWidget( QWidget * parent = nullptr);
protected:
//需要重新实现的事件
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent * event);
void dropEvent(QDropEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
private:
QPoint startDragPoint;
};
#endif // MYTREEWIDGET_H
- 源码文件
#include "mytreewidget.h"
#include <QMouseEvent>
#include <QApplication>
#include "dragimage.h"
#include "treeitemmimedata.h"
#include <QDrag>
#include "dragmodedialog.h"
MyTreeWidget::MyTreeWidget(QWidget *parent):QTreeWidget(parent)
{
this->setSelectionMode(QAbstractItemView::SingleSelection);
this->setDragEnabled(true);
this->viewport()->setAcceptDrops(true);
this->setDragDropMode(QAbstractItemView::InternalMove);
}
void MyTreeWidget::mousePressEvent(QMouseEvent *event)
{
if( event->button() == Qt::LeftButton )
{
startDragPoint = event->pos();
}
QTreeWidget::mousePressEvent(event);
}
void MyTreeWidget::mouseMoveEvent(QMouseEvent *event)
{
if( event->buttons() & Qt::LeftButton)//判断左键按下
{
int distance = (event->pos()-startDragPoint ).manhattanLength();
if( distance > QApplication::startDragDistance())
{
QTreeWidgetItem * item = this->currentItem();
DragImage *dragimag = new DragImage;
dragimag->setShowText(item->text(0));
dragimag->setShowIcon(item->icon(0));
QPixmap pixmap = dragimag->grab();
TreeItemMimeData *mimedata = new TreeItemMimeData;
mimedata->SetDragData("ItemMimeData",item);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimedata);
drag->setPixmap(pixmap);
drag->setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2));
if( drag->exec(Qt::MoveAction) == Qt::MoveAction)
{
delete item;
item = nullptr;
}
}
}
QTreeWidget::mouseMoveEvent(event);
}
void MyTreeWidget::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat("ItemMimeData") && event->source() == this )
{
event->setDropAction(Qt::MoveAction);
event->accept();
}
else
event->ignore();
}
void MyTreeWidget::dropEvent(QDropEvent *event)
{
if(event->mimeData()->hasFormat("ItemMimeData") && event->source() == this )
{
const TreeItemMimeData *pMimeData = (const TreeItemMimeData *)(event->mimeData());
const QTreeWidgetItem *item = pMimeData->DragItemData();
QTreeWidgetItem *pItem = item->clone();
QTreeWidgetItem *currentItem = this->itemAt(event->pos());
bool canceldrag = false;
if( currentItem == nullptr )
{
this->addTopLevelItem(pItem);
}
else
{
QTreeWidgetItem * currenParent = currentItem->parent();
DragModeDialog *dlg = new DragModeDialog;
dlg->setShowText(pItem->text(0),currentItem->text(0));
dlg->setShowPixm(pItem->icon(0).pixmap(20,20));
if( dlg->exec() == QDialog::Accepted)
{
DragModeDialog::DragMode dragmode = dlg->selMode;
switch (dragmode)
{
case DragModeDialog::DragAsChild:
currentItem->addChild(pItem);
break;
case DragModeDialog::DragToUp:
currenParent == nullptr ? insertTopLevelItem(indexOfTopLevelItem(currentItem),pItem) : currenParent->insertChild(currenParent->indexOfChild(currentItem),pItem);
break;
case DragModeDialog::DragToDown:
currenParent == nullptr ? insertTopLevelItem(indexOfTopLevelItem(currentItem)+1,pItem) : currenParent->insertChild(currenParent->indexOfChild(currentItem)+1,pItem);
break;
}
}
else
{
canceldrag = true;
}
}
if( !canceldrag)
{
event->setDropAction(Qt::MoveAction);
event->accept();
}
else
event->ignore();
}
else
event->ignore();
}
void MyTreeWidget::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("ItemMimeData") && event->source() == this )
{
const TreeItemMimeData *pMimeData = (const TreeItemMimeData *)(event->mimeData());
const QTreeWidgetItem *item = pMimeData->DragItemData();
QTreeWidgetItem *currentItem = this->itemAt(event->pos());
bool isok = true;
while(currentItem)
{
if(currentItem == item)
{
isok = false;
break;
}
currentItem = currentItem->parent();
}
if( isok )
{
event->setDropAction(Qt::CopyAction);
event->accept();
}
else
event->ignore();
}
else
event->ignore();
}
子类化 QMimeData
- 概述
通过子类化 QMimeData 实现自定义数据类型的传递。 - 头文件
#ifndef TREEITEMMIMEDATA_H
#define TREEITEMMIMEDATA_H
#include <QMimeData>
class QTreeWidgetItem;
class TreeItemMimeData : public QMimeData
{
Q_OBJECT
public:
TreeItemMimeData():QMimeData(){};
QStringList formats() const {return m_format ;}
const QTreeWidgetItem * DragItemData() const { return m_pDragItem;}
void SetDragData(QString mimeType,QTreeWidgetItem * pItem);
private:
QStringList m_format;
const QTreeWidgetItem * m_pDragItem;
protected:
QVariant retrieveData(const QString &mimetype, QVariant::Type preferredType) const ;
};
#endif // TREEITEMMIMEDATA_H
- 源码文件
#include "treeitemmimedata.h"
void TreeItemMimeData::SetDragData(QString mimeType, QTreeWidgetItem *pItem)
{
m_format << mimeType;
m_pDragItem = pItem;
}
QVariant TreeItemMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const
{
if( mimetype == "ItemMimeData")
{
return m_pDragItem;
}
else
return QMimeData::retrieveData(mimetype,preferredType);
}
拖拽过程图片显示
- 概述
使用 QPushButton 可以同时显示图标和文字,设置边框样式后可以达到需要的效果。 - 头文件
#ifndef DRAGIMAGE_H
#define DRAGIMAGE_H
#include <QWidget>
namespace Ui {
class DragImage;
}
class DragImage : public QWidget
{
Q_OBJECT
public:
explicit DragImage(QWidget *parent = nullptr);
~DragImage();
void setShowText(QString text);//拖动时显示的文字
void setShowIcon(QIcon pix);//拖动时显示的图标
private:
Ui::DragImage *ui;
};
#endif // DRAGIMAGE_H
- 源码文件
#include "dragimage.h"
#include "ui_dragimage.h"
DragImage::DragImage(QWidget *parent) :
QWidget(parent),
ui(new Ui::DragImage)
{
ui->setupUi(this);
this->setWindowFlags(Qt::FramelessWindowHint);
this->setAttribute(Qt::WA_TranslucentBackground, true);
}
DragImage::~DragImage()
{
delete ui;
}
void DragImage::setShowText(QString text)
{
ui->pushButton->setText(text);
}
void DragImage::setShowIcon(QIcon pix)
{
ui->pushButton->setIcon(pix);
}
拖拽操作选择界面
- 概述
使用 QButtonGroup 创建一个单选按钮集合,以选择拖拽的操作方式。 - 头文件
#ifndef DRAGMODEDIALOG_H
#define DRAGMODEDIALOG_H
#include <QDialog>
namespace Ui {
class DragModeDialog;
}
class QLabel;
class QButtonGroup;
class DragModeDialog : public QDialog
{
Q_OBJECT
public:
explicit DragModeDialog(QWidget *parent = nullptr);
~DragModeDialog();
void setShowText(const QString &dragname,const QString &desname);
void setShowPixm(const QPixmap &pix);
enum DragMode { DragAsChild,DragToUp,DragToDown };
DragMode selMode;
private:
Ui::DragModeDialog *ui;
QLabel * iconLabel;
QLabel * textlabel;
QButtonGroup * group;
private slots:
void buttonOkClick();
void buttonCancelClick();
};
#endif // DRAGMODEDIALOG_H
- 源码文件
#include "dragmodedialog.h"
#include "ui_dragmodedialog.h"
#include <QButtonGroup>
#include <QRadioButton>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QLabel>
#include <QGridLayout>
DragModeDialog::DragModeDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::DragModeDialog)
{
ui->setupUi(this);
//创建 QRadioButton 集合
group = new QButtonGroup(this);
QRadioButton * bnt1 = new QRadioButton(tr("子节点:添加到目标子节点末尾"),this);
QRadioButton * btn2 = new QRadioButton(tr("同级节点:添加到目标上方位置"),this);
QRadioButton * btn3 = new QRadioButton(tr("同级节点:添加到目标下方位置"),this);
bnt1->setChecked(true);
group->addButton(bnt1,1);
group->addButton(btn2,2);
group->addButton(btn3,3);
//创建标准按钮框
QDialogButtonBox * box = new QDialogButtonBox(this);
QPushButton * buttonOk = new QPushButton(tr("确定"));
QPushButton * buttonCancel = new QPushButton(tr("取消"));
box->addButton(buttonCancel,QDialogButtonBox::RejectRole);
box->addButton(buttonOk,QDialogButtonBox::YesRole);
buttonOk->setDefault(true);
iconLabel = new QLabel(this);
textlabel = new QLabel(this);
textlabel->setText(tr("选择拖动的行为:"));
textlabel->setStyleSheet("color:blue");
QFont ft;
ft.setBold(true);
ft.setPointSize(11);
textlabel->setFont(ft);
QLabel * tmp = new QLabel(this);
tmp->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
QGridLayout *mainLayout = new QGridLayout(this);
mainLayout->addWidget(iconLabel,0,0,1,1);
mainLayout->addWidget(textlabel,0,1,1,3);
mainLayout->addWidget(bnt1,1,0,1,4);
mainLayout->addWidget(btn2,2,0,1,4);
mainLayout->addWidget(btn3,3,0,1,4);
mainLayout->addWidget(tmp,4,0,1,4);
mainLayout->addWidget(box,5,0,1,4);
mainLayout->setHorizontalSpacing(10);
mainLayout->setVerticalSpacing(10);
mainLayout->setContentsMargins(10,10,10,10);
setLayout(mainLayout);
this->setFixedSize(380,210);
this->setWindowTitle(tr("拖动设置"));
setWindowFlags(Qt::WindowStaysOnTopHint);
connect(buttonOk,&QPushButton::clicked,this,&DragModeDialog::buttonOkClick);
connect(buttonCancel,&QPushButton::clicked,this,&DragModeDialog::buttonCancelClick);
}
DragModeDialog::~DragModeDialog()
{
delete ui;
}
void DragModeDialog::setShowText(const QString &dragname, const QString &desname)
{
textlabel->setText(QString(tr("拖动节点:%1,目标节点:%2,选择操作类型:")).arg(dragname).arg(desname));
}
void DragModeDialog::setShowPixm(const QPixmap &pix)
{
iconLabel->setPixmap(pix);
}
void DragModeDialog::buttonOkClick()
{
int selID = group->checkedId();
switch (selID)
{
case 1:
selMode = DragAsChild;
break;
case 2:
selMode = DragToUp;
break;
case 3:
selMode = DragToDown;
break;
}
accept();
}
void DragModeDialog::buttonCancelClick()
{
close();
}