前言
最近为了学习C++和qt5,跟着教程写了一个翻金币的小游戏,源码和资源文件的链接在这里:翻金币小游戏源码和资源文件的下载链接。里面有自己写的超多代码注释,只要2个C币,去秒。
作为第一个QT项目,还是有必要做些总结和一些细节方面的记录的。
承上一篇,这篇继续记录在项目中学到的知识。
事件(EVENT)
在qt中事件机制实现了两个功能:1.实现与用户操作的交互(如读取鼠标、键盘的输入信息)。2.实现窗口内的特定功能(绘图事件等)。可以从不同的应用场景出发来学习事件的使用方式。
绘图事件
绘图事件在程序中最基本的功能是绘制静态图像(绘制动态图像后面会讲),比如在主界面中我们需要给主界面添加一个背景,并且在背景中再叠加一个小图片当点缀,就应当通过主界面类中的绘图事件来实现:
void MainScene::paintEvent(QPaintEvent *event){//该事件用于设置窗口中的静态图像
//画背景
QPainter painter(this);
QPixmap pix;
pix.load(":/res/MenuSceneBg.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);//令背景图片适应窗口大小
//画背景上的其他图案
pix.load(":/res/Title.png");
pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);//图像大小不合适 需要按比例修改
painter.drawPixmap(10,30,pix);
}
在绘图事件中需要先实例化一个
QPainter
类的对象,将其加入对象树中,绘图事件中所有的图形都是由该对象绘制的。一般我们使用
Qpixmap
类存储对象,该类可支持常见的.png和.jpg图像。通过
QPixmap
类的load函数加载资源路径,并使用
drawPixmap
函数对图像的左上角坐标进行定位,并设置宽度和长度即可。
这里可以注意到,
drawPixmap
函数是通过传参定义图像坐标的,如果该坐标设置为一个变量,而不是常量,是不是就可以实现绘制动态的图像了呢?
实际上,如果没有其他操作的话,绘图事件函数只会在窗口初始化的时候调用一次,单纯将坐标设置为变量是不能实现动态图像绘制的。
这里挖个坑,在第二个项目飞机大战中再讲。
鼠标点击事件
鼠标点击事件是一大类事件的典型(鼠标相关事件、键盘相关事件等),因此在这里总结记录一下。
贴出一段学习代码:
void MyPushbutton::mousePressEvent(QMouseEvent *e){//鼠标按下的event handler
//额外判断,显然当第二张图片有传参时,使用该种效果,这样不会影响第一种效果
if(this->pressImgPath != ""){
QPixmap pix;
bool ret = pix.load(this->pressImgPath);//判断图片加载是否成功
if(!ret){//加载不成功的时候就终止程序
qDebug()<<"picture loading failed";
return;
}
//设定图像参数
this->setFixedSize(pix.width(),pix.height());//设定按钮的大小
this->setStyleSheet("QPushButton{border:0px;}");//设定轮廓,不然还是矩形按钮
this->setIcon(pix);//设定图标
this->setIconSize(QSize(pix.width(),pix.height()));//设定图标的大小
}
//稳妥起见,避免有其他鼠标按下事件存在,处理后应当抛给父类,让父类处理剩下的事件。
return QPushButton::mousePressEvent(e);
}
首先需要注意该鼠标点击事件是属于
MyPushbutton
类的,这里说明了两件事:
1.只有当鼠标移动到该类对象上再点击,才会触发该函数,实现函数内相关功能。
2.当鼠标在其他区域点击时,不应当触发该函数,而是实现其他区域设定的功能。
ok,那我们可以分开看看这两件事怎么实现。
第一件事在声明与实现这个函数的时候就默认实现了,是不需要去真正考虑的,只需要好好考虑鼠标点击后实现什么功能即可。
第二件事就需要用到QT内事件处理的机制了,当我们在一个事件处理函数中实现功能后,在最后一定要将该事件抛给父类的同样的事件处理函数,这样就能保住,当其他鼠标按下事件出现的时候,与我无关,让更高层次的父类看看怎么去处理,简称甩锅机制 。
实际上,如果不在函数中增加这句操作语句,在其他区域点击鼠标都会失去作用。
动画效果
在QT中动画效果只能依靠代码来实现。
掏出一段按钮向下跳动的代码:
void MyPushbutton::zoom1(){//动画函数 往下跳
QPropertyAnimation* animation = new QPropertyAnimation(this,"geometry");
animation->setDuration(200);//设定动画时间
animation->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));//初始位置
animation->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));//结束位置
animation->setEasingCurve(QEasingCurve::OutBounce);//动画动作方式
animation->start();
}
动画效果是依靠
QPropertyAnimation
类实现的,每一种动画效果都对应一个
QPropertyAnimation
类的对象。
在新建动画效果对象的时候,需要传参设定1.该动画对象绑定的类,2.具体形式的动画,这里我们使用位置变换的动画特效。
随后设定动画的起始位置与结束位置,在qt中坐标原点在窗口左上角,向右向下分别坐标相加,因此结束位置的坐标应该比起始坐标的y轴坐标数值更大。
随后设定动画的具体动作方式,设定为弹跳模式,就像橡皮球砸在地上要反弹一样。
最后写一下启动函数就好了。
不过这也衍生出了另一个问题:动画执行需要时间,而程序内的操作都是立即执行的,是不是该给动画“留点空”?
事实也确实如此,凡是添加了动画效果的组件在触发的时候都应该增加延时函数。
//因为特效动画需要时间,因此需要人为增加延时操作
QTimer::singleShot(400,this,[=](){//延时400ms后,触发后续的slot函数
chooseScene->setGeometry(this->geometry());//设定新窗口的坐标在屏幕上与原先窗口坐标相同
this->hide();//将当前界面隐藏
chooseScene->show();//切换到关卡选择界面
});
以上代码中可以看到,触发函数中使用
QTimer::singleShot
函数做了400ms的延时,专门为动画效果留了时间。