环境配置 :MinGW + QT 5.12 |
|
|
这种自定义拖拽样式的灵感来自于Chrome浏览器的书签栏。这篇文章命名为 “原生之初” 是因为没有加入 设置item在鼠标release时选中 以及 设置item在hover状态下改变图标样式 的代码,实现了最基本的自定义拖拽样式。本文中拖拽的特点是:拖拽即选中。
实现功能及方法:
拖拽时缩略图thumbnail类:
下面几篇文章除了 “原生之初” 都加入了 设置item在鼠标release时选中 以及 设置item在hover状态下改变图标样式 的代码:
(1)TestListView类继承自QListView(方法一)
class TestListView : public QListView
{
Q_OBJECT
public:
explicit TestListView(QWidget *parent = nullptr);
bool isDraging() const {return IsDraging;}
int offset() const {return 19;}
int highlightedRow() const {return theHighlightedRow;}
int dragRow() const {return theDragRow;}
static QString myMimeType() { return QStringLiteral("TestListView/text-icon"); }
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void startDrag(Qt::DropActions supportedActions) override;
private:
bool IsDraging = false;
int theHighlightedRow = -2;
int oldHighlightedRow = -2;
int theDragRow = -1;
int theInsertRow = -1;
};
TestListView::TestListView(QWidget *parent) :
QListView(parent)
{
// setMouseTracking(true);
setDragEnabled(true);
setAcceptDrops(true);
//setDropIndicatorShown(false);
setEditTriggers(QAbstractItemView::NoEditTriggers);
}
void TestListView::dragEnterEvent(QDragEnterEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this) {
IsDraging = true; //IsDraging(标志位)判断是否正在拖拽
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void TestListView::dragLeaveEvent(QDragLeaveEvent *event)
{
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = -2;
//之前QListWidget用的是update(QRect),这里用的是update(QModelIndex),当然这里也可以使用update(QRect),只是想换一种方法而已
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
IsDraging = false;
theInsertRow = -1;
event->accept();
}
void TestListView::dragMoveEvent(QDragMoveEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this) {
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = indexAt(event->pos() - QPoint(0, offset())).row();
//offset() = 19 = 40 / 2 - 1,其中40是行高
if(event->pos().y() >= offset()){
if(oldHighlightedRow != theHighlightedRow){
//刷新旧区域使dropIndicator消失
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
//刷新新区域使dropIndicator显示
update(model()->index(theHighlightedRow, 0));
update(model()->index(theHighlightedRow + 1, 0));
}else{
update(model()->index(theHighlightedRow, 0));
update(model()->index(theHighlightedRow + 1, 0));
}
theInsertRow = theHighlightedRow + 1;
}else{
theHighlightedRow = -1;
update(model()->index(0, 0));
update(model()->index(1, 0));
theInsertRow = 0;
}
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void TestListView::dropEvent(QDropEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this){
IsDraging = false; //完成拖拽
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = -2;
//刷新以使dropIndicator消失
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
if(theInsertRow == theDragRow || theInsertRow == theDragRow + 1) return;
//这里我像QListWidget那样调用父类dropEvent(event)发现不起作用(原因尚不明),没办法,只能删除旧行,插入新行
//if(theSelectedRow == theDragRow){
//QListView::dropEvent(event);
//return;
//}
//[1]从event->mimeData()取出拖拽数据
QString text;
QIcon icon;
QByteArray itemData = event->mimeData()->data(myMimeType());
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
dataStream >> text >> icon;
//[1]
model()->insertRow(theInsertRow); //插入新行
QStandardItemModel *listModel = qobject_cast<QStandardItemModel *>(model());
listModel->setItem(theInsertRow, 0, new QStandardItem(icon, text));
setCurrentIndex(model()->index(theInsertRow, 0)); //插入行保持选中状态
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
//使用startDrag()则不需要判断拖拽距离
void TestListView::startDrag(Qt::DropActions)
{
theDragRow = currentIndex().row();
QStandardItemModel *listModel = qobject_cast<QStandardItemModel *>(model());
QStandardItem *theDragItem = listModel->item(theDragRow);
//[1]把拖拽数据放在QMimeData容器中
QString text = theDragItem->text();
QIcon icon = theDragItem->icon();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << text << icon;
QMimeData *mimeData = new QMimeData;
mimeData->setData(myMimeType(), itemData);
//[1]
//[2]设置拖拽时的缩略图
thumbnail *DragImage = new thumbnail(this);
DragImage->setupthumbnail(icon, text);
//DragImage->setIconSize(18); //default:20
QPixmap pixmap = DragImage->grab();
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
drag->setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2));
//[2]
//删除的行需要根据theInsertRow和theDragRow的大小关系来判断(这个也是我根据实际情况测试发现的)
if(drag->exec(Qt::MoveAction) == Qt::MoveAction){
int theRemoveRow = -1;
if(theInsertRow < theDragRow) theRemoveRow = theDragRow + 1;
else theRemoveRow = theDragRow;
model()->removeRow(theRemoveRow);
}
}
(2)TestListView类继承自QListView(方法二)
class TestListView : public QListView
{
Q_OBJECT
public:
explicit TestListView(QWidget *parent = nullptr);
bool isDraging() const {return IsDraging;}
int offset() const {return 19;}
int highlightedRow() const {return theHighlightedRow;}
int dragRow() const {return theDragRow;}
static QString myMimeType() { return QStringLiteral("TestListView/text-icon-icon_hover"); }
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
QPoint startPos;
bool IsDraging = false;
int theHighlightedRow = -2;
int oldHighlightedRow = -2;
int theDragRow = -1;
int theInsertRow = -1;
};
TestListView::TestListView(QWidget *parent) :
QListView(parent)
{
// setMouseTracking(true);
// setDragEnabled(true);
setAcceptDrops(true);
//setDropIndicatorShown(false);
}
//记录拖拽初始位置
void TestListView::mousePressEvent(QMouseEvent *event)
{
QListView::mousePressEvent(event);
if(event->buttons() & Qt::LeftButton){
startPos = event->pos();
}
}
void TestListView::mouseMoveEvent(QMouseEvent *event)
{
if(event->buttons() & Qt::LeftButton){
if((event->pos() - startPos).manhattanLength() < QApplication::startDragDistance()) return;
QModelIndex theDragIndex = indexAt(startPos);
theDragRow = theDragIndex.row();
QStandardItemModel *listModel = qobject_cast<QStandardItemModel *>(model());
QStandardItem *theDragItem = listModel->item(theDragRow);
//[1]把拖拽数据放在QMimeData容器中
QString text = theDragItem->text();
QIcon icon = theDragItem->icon();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << text << icon;
QMimeData *mimeData = new QMimeData;
mimeData->setData(myMimeType(), itemData);
//[1]
//[2]设置拖拽时的缩略图
thumbnail *DragImage = new thumbnail(this);
DragImage->setupthumbnail(icon, text);
//DragImage->setIconSize(18); //default:20
QPixmap pixmap = DragImage->grab();
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
drag->setHotSpot(QPoint(pixmap.width() / 2, pixmap.height() / 2));
//[2]
//删除的行需要根据theInsertRow和theDragRow的大小关系来判断(这个也是我根据实际情况测试发现的)
if(drag->exec(Qt::MoveAction) == Qt::MoveAction){
int theRemoveRow = -1;
if(theInsertRow < theDragRow) theRemoveRow = theDragRow + 1;
else theRemoveRow = theDragRow;
model()->removeRow(theRemoveRow);
}
}
}
void TestListView::dragEnterEvent(QDragEnterEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this) {
IsDraging = true; //IsDraging(标志位)判断是否正在拖拽
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void TestListView::dragLeaveEvent(QDragLeaveEvent *event)
{
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = -2;
//之前QListWidget用的是update(QRect),这里用的是update(QModelIndex),当然这里也可以使用update(QRect),只是想换一种方法而已
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
IsDraging = false;
theInsertRow = -1;
event->accept();
}
void TestListView::dragMoveEvent(QDragMoveEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this) {
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = indexAt(event->pos() - QPoint(0, offset())).row();
//offset() = 19 = 40 / 2 - 1,其中40是行高
if(event->pos().y() >= offset()){
if(oldHighlightedRow != theHighlightedRow){
//刷新旧区域使dropIndicator消失
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
//刷新新区域使dropIndicator显示
update(model()->index(theHighlightedRow, 0));
update(model()->index(theHighlightedRow + 1, 0));
}else{
update(model()->index(theHighlightedRow, 0));
update(model()->index(theHighlightedRow + 1, 0));
}
theInsertRow = theHighlightedRow + 1;
}else{
theHighlightedRow = -1;
update(model()->index(0, 0));
update(model()->index(1, 0));
theInsertRow = 0;
}
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
void TestListView::dropEvent(QDropEvent *event)
{
TestListView *source = qobject_cast<TestListView *>(event->source());
if (source && source == this){
IsDraging = false; //完成拖拽
oldHighlightedRow = theHighlightedRow;
theHighlightedRow = -2;
//刷新以使dropIndicator消失
update(model()->index(oldHighlightedRow, 0));
update(model()->index(oldHighlightedRow + 1, 0));
if(theInsertRow == theDragRow || theInsertRow == theDragRow + 1) return;
//这里我像QListWidget那样调用父类dropEvent(event)发现不起作用(原因尚不明),没办法,只能删除旧行,插入新行
//if(theSelectedRow == theDragRow){
//QListView::dropEvent(event);
//return;
//}
//[1]从event->mimeData()取出拖拽数据
QString text;
QIcon icon;
QByteArray itemData = event->mimeData()->data(myMimeType());
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
dataStream >> text >> icon;
//[1]
model()->insertRow(theInsertRow); //插入新行
QStandardItemModel *listModel = qobject_cast<QStandardItemModel *>(model());
listModel->setItem(theInsertRow, 0, new QStandardItem(icon, text));
setCurrentIndex(model()->index(theInsertRow, 0)); //插入行保持选中状态
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
(3)TestItemDelegate类继承自QStyledItemDelegate,主要是为了绘制dropIndicator。图示为dropIndicator组成:
#define POLYGON 4 //等腰三角形直角边长
#define WIDTH 1 //分隔符粗细的一半
class TestItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
TestItemDelegate(QObject *parent = nullptr);
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
TestItemDelegate::TestItemDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
void TestItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
TestListView *dragView = qobject_cast<TestListView *>(option.styleObject);
bool isDraging = dragView->isDraging();
QRect rect = option.rect;
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen);
if(option.state & (QStyle::State_MouseOver | QStyle::State_Selected)){
if(option.state & QStyle::State_MouseOver){
}
if(option.state & QStyle::State_Selected){
painter->setBrush(QColor(180, 0, 0));
painter->drawRect(rect.topLeft().x(), rect.topLeft().y(), 4, rect.height());
painter->setBrush(QColor(230, 231, 234));
painter->drawRect(rect.topLeft().x() + 4, rect.topLeft().y(), rect.width() - 4, rect.height());
}
}
//begin drag
if(isDraging){
int theDragRow = dragView->dragRow();
int UpRow = dragView->highlightedRow();
int DownRow = UpRow + 1;
int rowCount = dragView->model()->rowCount() - 1;
//绘制DropIndicator
if(index.row() == UpRow && index.row() != theDragRow - 1 && index.row() != theDragRow){
painter->setBrush(QColor(66, 133, 244));
if(UpRow == rowCount){
//到达尾部,三角形向上移动一个WIDTH的距离,以使分隔符宽度*2
QPolygon trianglePolygon_bottomLeft;
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x(), rect.bottomLeft().y() - (POLYGON + WIDTH) + 1 - WIDTH);
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x(), rect.bottomLeft().y() - WIDTH + 1 - WIDTH);
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x() + POLYGON, rect.bottomLeft().y() - WIDTH + 1 - WIDTH);
QPolygon trianglePolygon_bottomRight;
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() + 1, rect.bottomRight().y() - (POLYGON + WIDTH) + 1 - WIDTH);
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() + 1, rect.bottomRight().y() - WIDTH + 1 - WIDTH);
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() - POLYGON + 1, rect.bottomRight().y() - WIDTH + 1 - WIDTH);
painter->drawRect(rect.bottomLeft().x(), rect.bottomLeft().y() - 2 * WIDTH + 1, rect.width(), 2 * WIDTH); //rect
painter->drawPolygon(trianglePolygon_bottomLeft);
painter->drawPolygon(trianglePolygon_bottomRight);
}
else {
//正常情况,组成上半部分(+1是根据实际情况修正)
QPolygon trianglePolygon_bottomLeft;
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x(), rect.bottomLeft().y() - (POLYGON + WIDTH) + 1);
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x(), rect.bottomLeft().y() - WIDTH + 1);
trianglePolygon_bottomLeft << QPoint(rect.bottomLeft().x() + POLYGON, rect.bottomLeft().y() - WIDTH + 1);
QPolygon trianglePolygon_bottomRight;
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() + 1, rect.bottomRight().y() - (POLYGON + WIDTH) + 1);
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() + 1, rect.bottomRight().y() - WIDTH + 1);
trianglePolygon_bottomRight << QPoint(rect.bottomRight().x() - POLYGON + 1, rect.bottomRight().y() - WIDTH + 1);
painter->drawRect(rect.bottomLeft().x(), rect.bottomLeft().y() - WIDTH + 1, rect.width(), WIDTH); //rect
painter->drawPolygon(trianglePolygon_bottomLeft);
painter->drawPolygon(trianglePolygon_bottomRight);
}
}
else if(index.row() == DownRow && index.row() != theDragRow + 1 && index.row() != theDragRow){
painter->setBrush(QColor(66, 133, 244));
if(DownRow == 0){
//到达头部,三角形向下移动一个WIDTH的距离,以使分隔符宽度*2
QPolygon trianglePolygon_topLeft;
trianglePolygon_topLeft << QPoint(rect.topLeft().x(), rect.topLeft().y() + (POLYGON + WIDTH) + WIDTH);
trianglePolygon_topLeft << QPoint(rect.topLeft().x(), rect.topLeft().y() + WIDTH + WIDTH);
trianglePolygon_topLeft << QPoint(rect.topLeft().x() + POLYGON, rect.topLeft().y() + WIDTH + WIDTH);
QPolygon trianglePolygon_topRight;
trianglePolygon_topRight << QPoint(rect.topRight().x() + 1, rect.topRight().y() + (POLYGON + WIDTH) + WIDTH);
trianglePolygon_topRight << QPoint(rect.topRight().x() + 1, rect.topRight().y() + WIDTH + WIDTH);
trianglePolygon_topRight << QPoint(rect.topRight().x() - POLYGON + 1, rect.topRight().y() + WIDTH + WIDTH);
painter->drawRect(rect.topLeft().x(), rect.topLeft().y(), rect.width(), 2 * WIDTH); //rect
painter->drawPolygon(trianglePolygon_topLeft);
painter->drawPolygon(trianglePolygon_topRight);
}
else{
//正常情况,组成下半部分(+1是根据实际情况修正)
QPolygon trianglePolygon_topLeft;
trianglePolygon_topLeft << QPoint(rect.topLeft().x(), rect.topLeft().y() + (POLYGON + WIDTH));
trianglePolygon_topLeft << QPoint(rect.topLeft().x(), rect.topLeft().y() + WIDTH);
trianglePolygon_topLeft << QPoint(rect.topLeft().x() + POLYGON, rect.topLeft().y() + WIDTH);
QPolygon trianglePolygon_topRight;
trianglePolygon_topRight << QPoint(rect.topRight().x() + 1, rect.topRight().y() + (POLYGON + WIDTH));
trianglePolygon_topRight << QPoint(rect.topRight().x() + 1, rect.topRight().y() + WIDTH);
trianglePolygon_topRight << QPoint(rect.topRight().x() - POLYGON + 1, rect.topRight().y() + WIDTH);
painter->drawRect(rect.topLeft().x(), rect.topLeft().y(), rect.width(), WIDTH); //rect
painter->drawPolygon(trianglePolygon_topLeft);
painter->drawPolygon(trianglePolygon_topRight);
}
}
QStyledItemDelegate::paint(painter, option, index);
return;
}
//end drag
QStyledItemDelegate::paint(painter, option, index);
}
(4)使用TestListWidget和TestItemDelegate
class test : public QWidget
{
Q_OBJECT
public:
explicit test(QWidget *parent = nullptr);
private:
void initUi();
};
test::test(QWidget *parent) : QWidget(parent)
{
initUi();
}
void test::initUi()
{
setFixedSize(250, 600);
QStandardItemModel *listModel = new QStandardItemModel();
listModel->setItem(0, 0, new QStandardItem(QIcon(":/listBar_Icon/1_hover.png"), "发现音乐"));
listModel->setItem(1, 0, new QStandardItem(QIcon(":/listBar_Icon/2_hover.png"), "私人FM"));
listModel->setItem(2, 0, new QStandardItem(QIcon(":/listBar_Icon/3_hover.png"), "朋友"));
listModel->setItem(3, 0, new QStandardItem(QIcon(":/listBar_Icon/4_hover.png"), "MV"));
listModel->setItem(4, 0, new QStandardItem(QIcon(":/listBar_Icon/5_hover.png"), "本地音乐"));
listModel->setItem(5, 0, new QStandardItem(QIcon(":/listBar_Icon/6_hover.png"), "下载管理"));
listModel->setItem(6, 0, new QStandardItem(QIcon(":/listBar_Icon/7_hover.png"), "我的音乐云盘"));
listModel->setItem(7, 0, new QStandardItem(QIcon(":/listBar_Icon/8_hover.png"), "我的收藏"));
TestListView *listView = new TestListView(this);
listView->setIconSize(QSize(25, 25));
listView->setFocusPolicy(Qt::NoFocus); //这样可禁用tab键和上下方向键并且除去复选框
listView->setFixedHeight(320);
listView->setFont(QFont("宋体", 10, QFont::DemiBold));
listView->setStyleSheet(
//"*{outline:0px;}" //除去复选框
"QListView{background:rgb(245, 245, 247); border:0px; margin:0px 0px 0px 0px;}"
"QListView::Item{height:40px; border:0px; padding-left:14px; color:rgba(200, 40, 40, 255);}"
"QListView::Item:hover{color:rgba(40, 40, 200, 255); padding-left:14px;}"
"QListView::Item:selected{color:rgba(40, 40, 200, 255); padding-left:15px;}"
);
TestItemDelegate *delegate = new TestItemDelegate();
listView->setItemDelegate(delegate);
listView->setModel(listModel);
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setSpacing(0);
layout->addWidget(listView);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
}
如果想要接触更多关于拖拽的代码,在Qt例程中搜索“drag”。推荐看一下例程puzzle的两种实现方法(一种是继承QListWidget,另一种是QListView + 继承QAbstractListModel)。
环境配置 :MinGW + QT 5.12 |