文章目录
前言
该篇作为学习Qt事件系统的前导篇,主要介绍最常见的三个事件的使用。
鼠标事件
参考文档:Drag and Drop
参考例程:Draggable Text Example
、 Draggable Icons Example
鼠标移动事件只会在按下鼠标按键的情况下才会发生,除非通过显式调用 QWidget::setMouseTracking()
函数来开启鼠标轨迹,这种情况下只要鼠标指针在移动,就会产生一系列的QT鼠标事件。
.h
#include <QMimeData>
#include <QDrag>
#include <QMouseEvent>
mousePressEvent
当鼠标按下时,该函数会被调用,这里我们获取控件的位置,并创建 QDrag
对象,QDrag
在 exec
前,一定要设置 QMimeData
,否则会弹出警告“QDrag: No mimedata set before starting the drag”。并且不会开始拖拽操作。
QMimeData
可用来保存拖拽操作附带的信息,比如字符串、文件或者图片,同时也可以用来验证其所保存的信息格式,并以此来判断是否可接收。未来再补充 QMimeData
的用法。
另外要注意,在 windows
下,QDrag::exec()
是个同步操作,要在 exec()
返回后,才会继续执行下面的代码。
void Mouseevent::mousePressEvent(QMouseEvent *event)
{
//childAt 返回QWidget中的x,y处的小部件,若无则返回null
QLabel *child = qobject_cast<QLabel*>(childAt(event->pos()));
if (!child)
return;
//移动偏移量:起始鼠标位 - 控制原位置
QPoint hotSpot = event->pos() - child->pos();
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setText(child->text());
mimeData->setData("hotSpot",
QByteArray::number(hotSpot.x()) + ' ' + QByteArray::number(hotSpot.y()));
drag->setMimeData(mimeData);
// QDrag::exec()是个同步操作,要在exec()返回后,才会继续执行下面的代码
Qt::DropAction dropAction = drag->exec(Qt::CopyAction | Qt::MoveAction);
//等待鼠标松开后的返回
if (dropAction == Qt::MoveAction)
child->close();
}
dragEnterEvent
首先,当需要一个控件接收 drag
和drop
,就要先调用控件的方法:setAcceptDrops(true)
。
当鼠标拖拽进入控件时,会触发 dragEnterEvent
,如果不做处理,后续将不会接收到 dragMoveEvent
事件和dragLeaveEvent
事件。在 dragEnterEvent
事件中,如果调用了 QDragMoveEvent::accept()
函数,后续将可以收到dragMoveEvent
事件和 dragLeaveEvent
事件。而如果调用 QDragMoveEvent::ignore()
函数,效果相当于不处理,不会接收后续事件。
setAcceptDrops(true);
void Mouseevent::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasText()) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
//忽略
event->ignore();
}
}
dropEvent
当 drag
为 accept
状态,然后释放鼠标,就会产生 dropEvent
。我们可以在这个事件里处理本次拖拽附带的Mime
信息。
void Mouseevent::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasText()) {
const QMimeData *mime = event->mimeData();
QString piece = mime->text();
QPoint position = event->pos();
QPoint hotSpot;
//获取位置偏移量
QByteArrayList hotSpotPos = mime->data("hotSpot").split(' ');
if (hotSpotPos.size() == 2) {
hotSpot.setX(hotSpotPos.first().toInt());
hotSpot.setY(hotSpotPos.last().toInt());
}
QLabel *newLabel = new QLabel(piece, this);
//设置位置:放下时鼠标位 - 偏移量(起始鼠标位-控件原位置)
newLabel->move(position - hotSpot);
newLabel->show();
event->accept();
}
}
键盘事件
.h
#include <QKeyEvent>
keyPressEvent
当我们按下按键时,会回调该函数。可在此函数中直接实现按键检测后的逻辑。
void Keyboardevent::keyPressEvent(QKeyEvent *event){
qDebug() << "keyPressEvent" << event->key();
int x = ui->pushButton->x();
int y = ui->pushButton->y();
if(event->key() == Qt::Key_Left)
{
x = x - STEPPING;
}
if(event->key() == Qt::Key_Right)
{
x = x + STEPPING;
}
if(event->key() == Qt::Key_Up)
{
y = y - STEPPING;
}
if(event->key() == Qt::Key_Down)
{
y = y + STEPPING;
}
ui->pushButton->move(x,y);
}
keyReleaseEvent
当我们松开按键时,会回调该函数。可在此函数中直接实现按键检测后的逻辑。
void Keyboardevent::keyReleaseEvent(QKeyEvent* event){
qDebug() << "keyReleaseEvent" << event->key();
int x = ui->pushButton->x();
int y = ui->pushButton->y();
if(event->key() == Qt::Key_Left)
{
x = x - STEPPING;
}
if(event->key() == Qt::Key_Right)
{
x = x + STEPPING;
}
if(event->key() == Qt::Key_Up)
{
y = y - STEPPING;
}
if(event->key() == Qt::Key_Down)
{
y = y + STEPPING;
}
ui->pushButton->move(x,y);
}
焦点问题
测试时发现,若检测方向键,焦点很容易被UI控件抢占,导致 ```keyPressEvent`` 未被调用。
//解决方向键焦点问题,否则方向键不触发keyPressEvent
ui->pushButton->setFocusPolicy(Qt::NoFocus);
多按键检测
原理
上面的代码只能实现单按键的检测。若先按下 ←
再按下 ↑
,其先调用的 keyPressEvent(左)
,再调用keyPressEvent(上)
,此后不断调用 keyReleaseEvent(上)
与 keyPressEvent(上)
,直到按键 ↑
退出,调用keyReleaseEvent(左)
。发现了吗,我们可以通过记录 keyPressEvent()
和 keyReleaseEvent()
的执行过程去判断当前那些按键被按下了。
实现
QList<int> mList;
void Keyboardevent::keyPressEvent(QKeyEvent *event){
qDebug() << "keyPressEvent" << event->key();
mList.push_back(event->key());
movePushButton();
}
void Keyboardevent::keyReleaseEvent(QKeyEvent* event){
qDebug() << "keyReleaseEvent" << event->key();
for(int i = 0;i < mList.count(); i++){
if(mList[i] == event->key()){
mList.removeAt(i);
}
}
}
void Keyboardevent::movePushButton(){
int x = ui->pushButton->x();
int y = ui->pushButton->y();
for(int i = 0;i < mList.count(); i++){
if(mList[i] == Qt::Key_Left)
{
x = x - STEPPING;
}
if(mList[i] == Qt::Key_Right)
{
x = x + STEPPING;
}
if(mList[i] == Qt::Key_Up)
{
y = y - STEPPING;
}
if(mList[i] == Qt::Key_Down)
{
y = y + STEPPING;
}
}
ui->pushButton->move(x,y);
}
改进
QList
存储可更换为HashMap
等以提高效率;由于这里仅需要保存四个按键,这里还可以使用一个int keystate
的位保存按键状态。- 由于我们是在按下按键时进行移动操作,既按下按键上,再按住按键左,先释放按键左,则按键上的
keyPressEvent
将不会再此被调用。解决方法也很简单,keyPressEvent()
、keyReleaseEvent()
单纯用来维护按键状态,使用定时器进行定时检查,在其中进行移动操作。
定时器事件
与上面的两个事件不同,这个事件的消息是系统产生的。
#include "Timeoutevent.h"
#include "ui_Timeoutevent.h"
#include <QDebug>
#include <QTime>
#include <ctime>
Timeoutevent::Timeoutevent(QWidget *parent) :
QWidget(parent),
ui(new Ui::Timeoutevent)
{
ui->setupUi(this);
mtimer = new QTimer;
connect(mtimer,&QTimer::timeout,this,&Timeoutevent::slot_timeout);
}
Timeoutevent::~Timeoutevent()
{
delete ui;
}
void Timeoutevent:: slot_timeout(){
qDebug() << "slot_timeout";
QTime time;
time= QTime::currentTime();
qsrand(time.msec()+time.second()*1000);
int n = qrand() % 5;
qDebug() << "伪随机数 qrand()" << n;
}
void Timeoutevent::on_pushButton_clicked()
{
if(!mtimer->isActive()){
mtimer->start(1000);
}
}
void Timeoutevent::on_pushButton_2_clicked()
{
mtimer->stop();
}