事件系统
The Event System
在Qt中,事件是派生自抽象QEvent类的对象,它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件尤其相关。
Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件,当事件发生时,Qt将创建一个事件对象。
事件是如何传递的?
当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它,并通过调用它的event()函数将它交付给QObject的一个特定实例(或它的一个子类)。
这个函数event()
不处理事件本身;根据交付的事件类型,它为该特定类型的事件调用事件处理程序,并根据事件是被接受还是被忽略发送响应。
一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;还有一些,比如QTimerEvent,来自其他来源;有些来自应用程序本身。
事件类型
大多数事件类型都有特殊的类,特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。每个类都是QEvent的子类,并添加特定于事件的函数。例如,QResizeEvent添加了size()和oldSize(),以使小部件能够发现它们的尺寸是如何被更改的。
有些类支持多个实际事件类型。QMouseEvent支持按下鼠标按钮、双击、移动和其他相关操作。
每个事件都有一个关联的类型,在QEvent:: type中定义,这可以用作运行时类型信息的方便来源,以快速确定给定事件对象是从哪个子类构建的。
事件处理
传递事件的通常方式是调用虚函数。例如,QPaintEvent通过调用QWidget::paintEvent()来传递。这个虚函数负责进行适当的响应,通常是重新绘制小部件。如果在虚函数的实现中不执行所有必要的工作,则可以调用基类的实现。
事件处理函数
例如,下面的代码处理自定义按钮控件上的鼠标左键单击,同时将所有其他按钮单击传递给基本QPushButton类:
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
//在这里处理鼠标左键
qInfo() << "left buttondown";
}
else
{
//将其他案件传递给基类处理
QPushButton::mousePressEvent(ev);
}
}
注意:如果这样做了,按钮的clicked()和pressed()信号将不能被触发!
class CustomButton : public QPushButton
{
public:
CustomButton(QWidget* parent = nullptr)
:QPushButton(parent)
{
//不能触发信号
connect(this, &QPushButton::clicked, this, []() {qInfo() << "clicked"; });
connect(this, &QPushButton::pressed, this, []() {qInfo() << "pressed"; });
}
void mousePressEvent(QMouseEvent* ev)override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
//在这里处理鼠标左键
qInfo() << "left buttondown";
}
else
{
//将其他案件传递给基类处理
QPushButton::mousePressEvent(ev);
}
}
};
想要让父类也能够处理左键消息,可以把父类的mousePressEvent放在最后调用(不放在else中)。
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
if (ev->button() == Qt::MouseButton::LeftButton)
{
//在这里处理鼠标左键
qInfo() << "left buttondown";
}
//将其他案件传递给基类处理
QPushButton::mousePressEvent(ev);
}
鼠标事件
鼠标按下
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
//获取鼠标按键 类型为枚举:Qt::MouseButton::
qInfo() << ev->button();
//如果同时有多个鼠标按键按下,需要判断左键是否按下
qInfo() << (ev->buttons() & Qt::MouseButton::LeftButton);
//获取鼠标坐标 position()返回浮点坐标,可以使用pos()获取整型坐标
qInfo() << ev->position(); //鼠标在本控件上的坐标
qInfo() << ev->scenePosition(); //鼠标在本控件所在的窗口位置
qInfo() << ev->globalPosition(); //鼠标相对于屏幕的坐标
//将其他案件传递给基类处理
QPushButton::mousePressEvent(ev);
}
鼠标释放
void CustomButton::mouseReleaseEvent(QMouseEvent*ev)override
{
QPushButton::mouseReleaseEvent(ev);
}
鼠标双击
void CustomButton::mouseDoubleClickEvent(QMouseEvent*ev)override
{
qInfo() << __FUNCTION__;
}
鼠标移动
如果关闭了鼠标跟踪,则只有在移动鼠标时按下鼠标按钮时才会发生鼠标移动事件。如果打开了鼠标跟踪,即使没有按下鼠标按钮,也会发生鼠标移动事件。
按钮是自动追踪鼠标的,但是QWidget是不会的,如果要给QWidget的子类重写鼠标移动,需要使用void setMouseTracking(bool enable)
启用鼠标追踪。
void CustomButton::mouseMoveEvent(QMouseEvent* ev)override { qInfo() << __FUNCTION__ << ev->pos(); }
鼠标滚轮
返回轮子旋转的相对量,单位为八分之一度。正值表示转轮向前旋转,远离用户;负值表示转轮向后向用户旋转。angleDelta().y()提供自上一个事件以来旋转普通垂直鼠标滚轮的角度。如果鼠标有水平滚轮,angleDelta().x()提供水平鼠标滚轮旋转的角度,否则就是0。有些鼠标允许用户倾斜滚轮来进行水平滚动,有些触摸板支持水平滚动手势;它也会出现在angleDelta().x()中。
大多数鼠标类型的工作步长为15度,在这种情况下,delta值是120的倍数;即120单位* 1/8 = 15度。
然而,有些鼠标的滚轮分辨率更高,发送的delta值小于120单位(小于15度)。为了支持这种可能性,可以累计添加来自事件的增量值,直到达到120的值,然后滚动小部件,或者可以部分滚动小部件以响应每个轮事件。
void CustomButton::wheelEvent(QWheelEvent* ev)override
{
//获取滚轮滚动方向
QPoint numDegrees = ev->angleDelta();
qInfo() <<"水平:" << numDegrees.x()/8 <<"垂直:" << numDegrees.y()/8;
}
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowFlag(Qt::WindowType::FramelessWindowHint);
//setMouseTracking(true); //test
}
void mousePressEvent(QMouseEvent*ev)override
{
pressPos = ev->pos();
}
void mouseMoveEvent(QMouseEvent* ev)override
{
if (ev->buttons() & Qt::MouseButton::LeftButton)
{
move(ev->globalPosition().toPoint() - pressPos);
}
//qInfo() << __FUNCTION__;
}
private:
QPoint pressPos;
};
键盘事件
按键按下
void keyPressEvent(QKeyEvent* ev)override
{
//获取按键
qInfo() <<"key:" << Qt::Key(ev->key());
//返回此键生成的Unicode文本。不同平台按下Shift、Control、Alt和Meta等修饰键时的返回值不同,可能返回空字符串。
qInfo() << "text:" << ev->text();
//获取按下了什么键盘修饰符(shift、control、alt、meta(windows键)、keypad(小键盘,即数字键))
qInfo() << ev->modifiers();
//1,判断是否按下了组合键(快捷键、加速键)
if (ev->modifiers() & Qt::KeyboardModifier::ControlModifier && ev->key() == Qt::Key::Key_S)
{
qInfo() << "1 保存,保存";
}
//2,判断是否按下了组合键,方便的函数
if (ev->matches(QKeySequence::StandardKey::Save))
{
qInfo() <<"2 保存、保存";
}
}
Example:按钮移动
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr)
:QWidget(parent)
{
setWindowFlag(Qt::WindowType::FramelessWindowHint);
resize(640, 480);
button = new QPushButton("玩蛇", this);
button->setFocusPolicy(Qt::FocusPolicy::NoFocus);
}
void keyPressEvent(QKeyEvent* ev)override
{
switch (ev->key())
{
case Qt::Key::Key_Up:
button->move(button->pos() + QPoint(0, -1));
break;
case Qt::Key::Key_Down:
button->move(button->pos() + QPoint(0, 1));
break;
case Qt::Key::Key_Left:
button->move(button->pos() + QPoint(-1, 0));
break;
case Qt::Key::Key_Right:
button->move(button->pos() + QPoint(1, 0));
break;
}
}
private:
QPushButton* button = nullptr;
};
窗口事件
窗口关闭
当Qt从窗口系统接收到一个顶级小部件的窗口关闭请求时,将用给定的事件调用此事件处理程序。
默认情况下,接受事件并关闭小部件。您可以重新实现此函数,以更改小部件响应窗口关闭请求的方式。例如,您可以通过在所有事件上调用ignore()来防止窗口关闭。
主窗口应用程序通常使用该函数的重新实现来