0


Qt编写可拖拽的自定义控件

一直想做一个像卡牌游戏一样的,可以拖动卡片,实现改变位置,顺序交换的效果,今天我们一起来尝试一下。

1.先绘制一个基于QWidget的控件

类名为Card
h文件

#ifndefCARD_H#defineCARD_H#include<QWidget>#include<QPaintEvent>#include<QPainter>classCard:publicQWidget{
    Q_OBJECT
public:explicitCard(QWidget *parent =nullptr);protected:voidpaintEvent(QPaintEvent *event)override;};#endif// CARD_H

cpp文件

#include"card.h"Card::Card(QWidget *parent):QWidget(parent){this->setGeometry(0,0,200,400);}voidCard::paintEvent(QPaintEvent *event){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing,true);
    painter.drawRoundedRect(QRectF(5,5,190,390),10,10);}

我们完成了一个很简单的200*400的圆角卡片
在主界面中展示看看
widget.h

#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>#include"card.h"
QT_BEGIN_NAMESPACE
namespace Ui {classWidget;}
QT_END_NAMESPACE

classWidget:publicQWidget{
    Q_OBJECT

public: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<8;i++){
        cd[i]=newCard(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

#ifndefCARD_H#defineCARD_H#include<QWidget>#include<QPaintEvent>#include<QPainter>#include<QMouseEvent>classCard:publicQWidget{
    Q_OBJECT
public:explicitCard(QWidget *parent =nullptr);protected:voidpaintEvent(QPaintEvent *event)override;voidmousePressEvent(QMouseEvent *event)override;
signals:voidsendSelf(Card *w);};#endif// CARD_H

我们新增了mousePressEvent和一个信号sendSelf,信号将自己本身的地址发送出去

card.cpp

#include"card.h"Card::Card(QWidget *parent):QWidget(parent){this->setGeometry(0,0,200,400);}voidCard::paintEvent(QPaintEvent *event){
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing,true);
    painter.drawRoundedRect(QRectF(5,5,190,390),10,10);}voidCard::mousePressEvent(QMouseEvent *event){Q_UNUSED(event);
    emit sendSelf(this);}

到widget.h中设置相应的槽函数

private slots:voidgetObject(Card *w);

接收到对象后,我们接下来要对其进行操作,改变位置只需要用move()函数即可,我们要实时显示出移动的过程,因此需要在mouseMoveEvent中做处理,重写mouseMoveEvent函数:

protected:voidmouseMoveEvent(QMouseEvent *event)override;

还需要声明一个Card成员变量,来临时存储getObject中获得的对象地址,声明一个开始移动时鼠标指针位置startP,一个卡片本身的位置yuanP。

private:
    Ui::Widget *ui;
    Card *temp;
    QPoint startP;
    QPoint yuanP;

实现getObject函数和mouseMoveEvent函数
widget.cpp

voidWidget::getObject(Card *w){
    temp = w;
    startP =cursor().pos()-this->pos();
    yuanP = temp->pos();}voidWidget::mouseMoveEvent(QMouseEvent *event){
    temp->move(yuanP.x()+event->x()-startP.x(),yuanP.y()+event->y()-startP.y());}

运行后,可以成功拖动卡片了!
但是我们又发现了一个问题,当卡片重叠时,我们点击重叠部分无法确定选中的是哪张卡片,因此我们制定一个规则,最近移动的卡片总是处于最上层。我们可以在mouseReleaseEvent中做处理:

voidWidget::mouseReleaseEvent(QMouseEvent *event){
    temp->raise();}

到这一步,我们已经实现了可自由拖拽改变位置的控件。

3.接下来实现拖拽交换卡片位置的功能

这里我制定了一个规则,拖拽某张卡片时,若此时的鼠标指针进入了另一张卡片范围,则进行交换。

整体思路是这样的:

在widget中的mouseMoveEvent中做处理:当鼠标移动时,给Card发送信号,由Card的槽函数中做判断,指针是否进入了自身范围if(this->geometry().contains(pos)),如果进入了,就向widget发送信号,避免重复发送信号,增加一个开关量,widget收到反馈信号后就关闭。为了避免拖拽的Card本身触发进入范围的判断,在widget的getObject槽函数中先关闭了当前Card的连接。
为了能看清是否交换位置了,我们给card增加了标号显示。
完成后的代码:
card.h

#ifndefCARD_H#defineCARD_H#include<QWidget>#include<QPaintEvent>#include<QPainter>#include<QMouseEvent>classCard:publicQWidget{
    Q_OBJECT
public:explicitCard(QWidget *parent =nullptr);
    QString txt;//显示卡片标号public slots:voidgetPos(QPoint p);//接收widget发送的鼠标坐标protected:voidpaintEvent(QPaintEvent *event)override;voidmousePressEvent(QMouseEvent *event)override;private:bool isDragging=false;//当前是否被拖拽
signals:voidsendSelf(Card *w);voidsendNeedChange(Card *w);//发送给widget表明自己需要被交换};#endif// CARD_H

card.cpp

#include"card.h"#include<QDebug>Card::Card(QWidget *parent):QWidget(parent){this->setGeometry(0,0,200,400);}voidCard::getPos(QPoint p){if(this->geometry().contains(p)){qDebug()<<"enter"<<txt;
        emit sendNeedChange(this);}}voidCard::paintEvent(QPaintEvent *event){
    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);//绘制卡片标号}voidCard::mousePressEvent(QMouseEvent *event){Q_UNUSED(event);
    isDragging =true;
    emit sendSelf(this);}

widget.h

#ifndefWIDGET_H#defineWIDGET_H#include<QWidget>#include<QMouseEvent>#include"card.h"
QT_BEGIN_NAMESPACE
namespace Ui {classWidget;}
QT_END_NAMESPACE

classWidget:publicQWidget{
    Q_OBJECT

public:Widget(QWidget *parent =nullptr);~Widget();private slots:voidgetObject(Card *w);voidneedChange(Card *w);//执行交换protected:voidmouseMoveEvent(QMouseEvent *event)override;voidmouseReleaseEvent(QMouseEvent *event)override;private:
    Ui::Widget *ui;
    Card* cd[8];
    Card *temp;

    QPoint startP;
    QPoint yuanP;
    QRect yuanR;bool isMoving=false;
signals:voidsendPos(QPoint p);//发送鼠标坐标};#endif// WIDGET_H

widget.cpp

#include"widget.h"#include"ui_widget.h"#include<QDebug>Widget::Widget(QWidget *parent):QWidget(parent),ui(new Ui::Widget){
    ui->setupUi(this);for(int i=0;i<8;i++){
        cd[i]=newCard(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;}voidWidget::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;}voidWidget::needChange(Card *w){
    targetR = w->geometry();//记录被交换对象的位置
    w->setGeometry(yuanR);//被交换的card移动到被拖拽的card的原位置
    isMoving=false;}voidWidget::mouseMoveEvent(QMouseEvent *event){
    temp->move(yuanP.x()+event->x()-startP.x(),yuanP.y()+event->y()-startP.y());if(isMoving){
        emit sendPos(event->pos());}}voidWidget::mouseReleaseEvent(QMouseEvent *event){connect(this,&Widget::sendPos,temp,&Card::getPos);//交换完成恢复连接
    temp->raise();qDebug()<<targetR;
    temp->setGeometry(targetR);//松开后被拖拽card自动调整到被交换card的原位置}

到这一步就已经基本实现我们最初想要的功能了,接下来可以再移动过程中加点动画效果,把widget也封装起来,可以自定义设置卡片数量,卡片尺寸等

完整代码链接:Qt自定义可拖拽控件

03.01更新:增加的动画效果

利用QPropertyAnimation实现卡片的动画移动效果
card.h

#ifndefCARD_H#defineCARD_H#include<QWidget>#include<QPaintEvent>#include<QPainter>#include<QMouseEvent>#include<QPropertyAnimation>classCard:publicQWidget{
    Q_OBJECT
public:explicitCard(QWidget *parent =nullptr);
    QString txt;voidmoveTo(QRect r);//新增移动接口,r为目标QRectpublic slots:voidgetPos(QPoint p);protected:voidpaintEvent(QPaintEvent *event)override;voidmousePressEvent(QMouseEvent *event)override;private slots:voidreset();//动画结束后触发private:bool isDragging=true;//拖拽标志,防止重复触发动画
signals:voidsendSelf(Card *w);voidsendNeedChange(Card *w);};#endif// CARD_H

card.cpp

voidCard::moveTo(QRect r){
    QPropertyAnimation *animation =newQPropertyAnimation(this,"geometry");connect(animation,&QPropertyAnimation::finished,this,&Card::reset);
    animation->setDuration(300);
    animation->setStartValue(this->geometry());
    animation->setEndValue(r);
    animation->start();}voidCard::getPos(QPoint p){if(isDragging &&this->geometry().contains(p)){
        isDragging=false;qDebug()<<"enter"<<txt;
        emit sendNeedChange(this);}}voidCard::reset(){
    isDragging =true;}
标签: qt ui

本文转载自: https://blog.csdn.net/qq_37033647/article/details/129126427
版权归原作者 戏言zare 所有, 如有侵权,请联系我们删除。

“Qt编写可拖拽的自定义控件”的评论:

还没有评论