行业知识
Qt编写可拖拽的自定义控件
一���想做一个像卡牌游戏一样的,可以拖动卡片,实现改变位置,顺序交换的效果,今天我们一起来尝试一下。
1.先绘制一个基于QWidget的控件
类名为Card
h文件
#ifndef CARD_H#define CARD_H#include #include #include class Card : public QWidget{ Q_OBJECTpublic: explicit Card(QWidget *parent = nullptr);protected: void paintEvent(QPaintEvent *event) override;};#endif // CARD_H cpp文件
#include "card.h"Card::Card(QWidget *parent) : QWidget(parent){ this->setGeometry(0,0,200,400);}void Card::paintEvent(QPaintEvent *event){ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); painter.drawRoundedRect(QRectF(5,5,190,390),10,10);} 我们完成了一个很简单的200*400的圆角卡片
在主界面中展示看看
widget.h
#ifndef WIDGET_H#define WIDGET_H#include #include "card.h"QT_BEGIN_NAMESPACEnamespace Ui { class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget();private: Ui::Widget *ui; Card* cd[8];};#endif // WIDGET_H widget.cpp
#include "widget.h"#include "ui_widget.h"Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget){ ui->setupUi(this); for(int i=0;i cd[i] = new Card(this); connect(cd[i],&Card::sendSelf,this,&Widget::getObject); cd[i]-move(i%4*200,i/4*400); }}Widget::~Widget(){ delete ui;} 运行后的效果:
2.用QMouseEvent实现控件可拖动
首先要实现控件拖动,需要有2个要素,1:要拖动的控件对象,2:控件的初始位置
card[8]是以数组形式一次性加载到界面的,鼠标点击时我们并不知道当前点击的对象。我们可以在Card类中做修改,使点击时通知主界面它是谁。
card.h
#ifndef CARD_H#define CARD_H#include #include #include #include class Card : public QWidget{ Q_OBJECTpublic: explicit Card(QWidget *parent = nullptr);protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override;signals: void sendSelf(Card *w);};#endif // CARD_H 我们新增了mousePressEvent和一个信号sendSelf,信号将自己本身的地址发送出去
card.cpp
#include "card.h"Card::Card(QWidget *parent) : QWidget(parent){ this->setGeometry(0,0,200,400);}void Card::paintEvent(QPaintEvent *event){ QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); painter.drawRoundedRect(QRectF(5,5,190,390),10,10);}void Card::mousePressEvent(QMouseEvent *event){ Q_UNUSED(event); emit sendSelf(this);} 到widget.h中设置相应的槽函数
private slots: void getObject(Card *w);
接收到对象后,我们接下来要对其进行操作,改变位置只需要用move()函数即可,我们要实时显示出移动的过程,因此需要在mouseMoveEvent中做处理,重写mouseMoveEvent函数:
protected: void mouseMoveEvent(QMouseEvent *event) override;
还需要声明一个Card成员变量,来临时存储getObject中获得的对象地址,声明一个开始移动时鼠标指针位置startP,一个卡片本身的位置yuanP。
private: Ui::Widget *ui; Card *temp; QPoint startP; QPoint yuanP;
实现getObject函数和mouseMoveEvent函数
widget.cpp
void Widget::getObject(Card *w){ temp = w; startP = cursor().pos()-this->pos(); yuanP = temp->pos();}void Widget::mouseMoveEvent(QMouseEvent *event){ temp->move(yuanP.x()+event->x()-startP.x(),yuanP.y()+event->y()-startP.y());} 运行后,可以成功拖动卡片了!
但是我们又发现了一个问题,当卡片重叠时,我们点击重叠部分无法确定选中的是哪张卡片,因此我们制定一个规则,最近移动的卡片总是处于最上层。我们可以在mouseReleaseEvent中做处理:
void Widget::mouseReleaseEvent(QMouseEvent *event){ temp->raise();} 到这一步,我们已经实现了可自由拖拽改变位置的控件。
3.接下来实现拖拽交换卡片位置的功能
这里我制定了一个规则,拖拽某张卡片时,若此时的鼠标指针进入了另一张卡片范围,则进行交换。
整体思路是这样的:
在widget中的mouseMoveEvent中做处理:当鼠标移动时,给Card发送信号,由Card的槽函数中做判断,指针是否进入了自身范围if(this->geometry().contains(pos)),如果进入了,就向widget发送信号,避免重复发送信号,增加一个开关量,widget收到反馈信号后就关闭。为了避免拖拽的Card本身触发进入范围的判断,在widget的getObject槽函数中先关闭了当前Card的连接。
为了能看清是否交换位置了,我们给card增加了标号显示。
完成后的代码:
card.h
#ifndef CARD_H#define CARD_H#include #include #include #include class Card : public QWidget{ Q_OBJECTpublic: explicit Card(QWidget *parent = nullptr); QString txt;//显示卡片标号public slots: void getPos(QPoint p);//接收widget发送的鼠标坐标protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override;private: bool isDragging=false;//当前是否被拖拽signals: void sendSelf(Card *w); void sendNeedChange(Card *w);//发送给widget表明自己需要被交换};#endif // CARD_H card.cpp
#include "card.h"#include Card::Card(QWidget *parent) : QWidget(parent){ this->setGeometry(0,0,200,400);}void Card::getPos(QPoint p){ if(this->geometry().contains(p)){ qDebug() QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); painter.drawRoundedRect(QRectF(5,5,190,390),10,10); painter.drawText(50,100,100,200,Qt::AlignCenter,txt);//绘制卡片标号}void Card::mousePressEvent(QMouseEvent *event){ Q_UNUSED(event); isDragging = true; emit sendSelf(this);} class Widget; }QT_END_NAMESPACEclass Widget : public QWidget{ Q_OBJECTpublic: Widget(QWidget *parent = nullptr); ~Widget();private slots: void getObject(Card *w); void needChange(Card *w);//执行交换protected: void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override;private: Ui::Widget *ui; Card* cd[8]; Card *temp; QPoint startP; QPoint yuanP; QRect yuanR; bool isMoving=false;signals: void sendPos(QPoint p);//发送鼠标坐标};#endif // WIDGET_H ui-setupUi(this); for(int i=0;i cd[i] = new Card(this); cd[i]-txt=QString::number(i+1);//绘制卡片标号 connect(cd[i],&Card::sendSelf,this,&Widget::getObject); connect(cd[i],&Card::sendNeedChange,this,&Widget::needChange); connect(this,&Widget::sendPos,cd[i],&Card::getPos); cd[i]-move(i%4*200,i/4*400); }}Widget::~Widget(){ delete ui;}void Widget::getObject(Card *w){ temp = w; disconnect(this,&Widget::sendPos,w,&Card::getPos);//暂时断开正在拖拽的card连接,避免触发与自己的交换 startP = cursor().pos()-this-pos(); yuanP = temp-pos(); yuanR = temp->geometry(); isMoving=true;}void Widget::needChange(Card *w){ targetR = w->geometry();//记录被交换对象的位置 w->setGeometry(yuanR);//被交换的card移动到被拖拽的card的原位置 isMoving=false;}void Widget::mouseMoveEvent(QMouseEvent *event){ temp->move(yuanP.x()+event->x()-startP.x(),yuanP.y()+event->y()-startP.y()); if(isMoving){ emit sendPos(event->pos()); }}void Widget::mouseReleaseEvent(QMouseEvent *event){ connect(this,&Widget::sendPos,temp,&Card::getPos);//交换完成恢复连接 temp->raise(); qDebug() Q_OBJECTpublic: explicit Card(QWidget *parent = nullptr); QString txt; void moveTo(QRect r);//新增移动接口,r为目标QRectpublic slots: void getPos(QPoint p);protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override;private slots: void reset();//动画结束后触发private: bool isDragging=true;//拖拽标志,防止重复触发动画signals: void sendSelf(Card *w); void sendNeedChange(Card *w);};#endif // CARD_H QPropertyAnimation *animation = new QPropertyAnimation(this,"geometry"); connect(animation,&QPropertyAnimation::finished,this,&Card::reset); animation-setDuration(300); animation->setStartValue(this->geometry()); animation->setEndValue(r); animation->start();}void Card::getPos(QPoint p){ if(isDragging && this->geometry().contains(p)){ isDragging=false; qDebug() isDragging = true;}