QEvent
类,常见的有键盘事件QKeyEvent
、鼠标事件QMouseEvent
和定时器事件QTimerEvent
等,它们与QEvent类的继承关系如图所示。 `QEvent
类。QMouseEvent
(不是按钮产生的),而因为按钮被按下了,所以它会发出clicked()
单击信号(是按钮产生的)。这里一般只关心按钮的单击信号,而不用考虑鼠标事件,但是如果要设计一个按钮,或者当鼠标点击按钮时让它产生别的效果,那么就要关心鼠标事件了。可以看到,事件与信号是两个不同层面的东西,它们的发出者不同,作用也不同。在Qt中,任何QObject
的子类的实例都可以接收和处理事件。事件有主窗口相关的;也有部件相关事件。
paintEvent()
,mousePressEvent()
等事件处理函数。这是最常用也的一种方法,不过它只能用来处理特定部件的特定事件。例如前一章实现拖放操作,就是用的这种方法。新建一个类去继承基类,重新写该类的事件处理函数。部件一般不自动建新类,但是主窗口在定义好之后实际上已经建好了新类,是区分于QWidget、QDialog、QMainwindow的。
notify()
函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。QApplication
对象上安装事件过滤器。因为一个程序只有一个QApplication
对象,所以这样实现的功能与使用notify()
函数是相同的,优点是可以同时处理多个事件。event()
函数。QObject类的event()
函数可以在事件到达默认的事件处理函数之前获得该事件。在MyLineEdit
中添加键盘按下事件处理函数声明:
protected:
void keyPressEvent(QKeyEvent *event);
事件处理函数的定义:
//MyLineEdit类继承自Qt自带的QLineEdit类
void MyLineEdit::keyPressEvent(QKeyEvent *event) // 键盘按下事件
{
qDebug() << tr("MyLineEdit键盘按下事件");
QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理
event->ignore(); // 忽略该事件
}
在Widget主窗口中添加键盘按下事件处理函数声明:
protected:
void keyPressEvent(QKeyEvent *event);
主窗口事件处理函数定义
void Widget::keyPressEvent(QKeyEvent *event)
{
qDebug() << tr("Widget键盘按下事件");
}
MyLineEdit
),但是如果该部件忽略掉该事件(event->ignore()
) ,事件就会传递给该窗口部件的父部件(Widget
),相应也会执行父部件的键盘按下事件函数。在重新实现事件处理函数时,一般要调用父类的相应的事件处理函数来实现默认的操作。在MyLineEdit中添加函数声明:
bool event(QEvent *event);
该函数定义:
bool MyLineEdit::event(QEvent *event) // MyLineEdit事件函数
{
if(event->type() == QEvent::KeyPress)
qDebug() << tr("MyLineEdit的event()函数");
return QLineEdit::event(event); //执行QLineEdit类event()函数的默认操作
}
QEvent
的type()
函数来获取事件的类型,如果是键盘按下事件QEvent::KeyPress
,则输出信息。因为event()
函数具有bool
型的返回值,所以在该函数的最后要使用return
语句,这里一般是返回父类的event()
函数的操作结果。bool eventFilter(QObject *obj, QEvent *event);
在widget.cpp文件中的构造函数的最后添上一行代码:
lineEdit->installEventFilter(this); // 在Widget上为lineEdit安装事件过滤器
下面是事件过滤器函数的定义:
bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器
{
if(obj == lineEdit) { // 如果是lineEdit部件上的事件
if(event->type() == QEvent::KeyPress)
qDebug() << tr("Widget的事件过滤器");
}
return QWidget::eventFilter(obj, event);
}
在事件过滤器中,先判断该事件的对象是不是lineEdit
,如果是,再判断事件类型。最后返回了QWidget
类默认的事件过滤器的执行结果。
事件的传递顺序是这样的:先是事件过滤器,然后是该部件的event()
函数,最后是该部件的事件处理函数。这里还要注意,event()
函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。
QMouseEvent
类用来表示一个鼠标事件,当在窗口部件中按下鼠标或者移动鼠标指针时,都会产生鼠标事件。利用QMouseEvent
类可以获知鼠标是哪个键按下了,还有鼠标指针的当前位置等信息。通常是重定义部件的鼠标事件处理函数来进行一些自定义的操作。QMouseEvent
类用来表示一个鼠标事件,当在窗口部件中按下鼠标或者移动鼠标指针时,都会产生鼠标事件。利用QMouseEvent
类可以获知鼠标是哪个键按下了,还有鼠标指针的当前位置等信息。通常是重定义部件的鼠标事件处理函数来进行一些自定义的操作。void Widget::mousePressEvent(QMouseEvent *event) // 主窗口的鼠标按下事件(非部件)
{
if(event->button() == Qt::LeftButton){ // 如果是鼠标左键按下
QCursor cursor;
cursor.setShape(Qt::ClosedHandCursor);
QApplication::setOverrideCursor(cursor); // 使鼠标指针暂时改变形状
offset = event->globalPos() - pos(); // 获取指针位置和窗口位置的差值
}
else if(event->button() == Qt::RightButton){ // 如果是鼠标右键按下
QCursor cursor(QPixmap("../yafeilinux.png"));
QApplication::setOverrideCursor(cursor);// 使用自定义的图片作为鼠标指针
}
}
globalPos()
函数来获取鼠标指针的位置,这个位置是指针在桌面上的位置,因为窗口的位置就是指的它在桌面上的位置。另外,还可以使用QMouseEvent
类的pos()
函数获取鼠标指针在窗口中的位置。如果是鼠标右键按下,那么就将指针显示为我们自己的图片。void Widget::mouseMoveEvent(QMouseEvent *event) // 鼠标移动事件
{
if(event->buttons() & Qt::LeftButton){ // 在移动过程中,这里必须使用buttons()
QPoint temp;
temp = event->globalPos() - offset;
move(temp);// 使用鼠标指针当前的位置减去差值,就得到了窗口应该移动的位置
}
}
在鼠标移动时,会检测所有按下的键,而这时使用QMouseEvent的button()函数无法获取哪个按键被按下
,只能使用buttons()函数,所以这里使用buttons()和Qt::LeftButton进行按位与的方法来判断是否是鼠标左键按下。void Widget::mouseReleaseEvent(QMouseEvent *event) // 鼠标释放事件
{
QApplication::restoreOverrideCursor(); // 恢复鼠标指针形状
}
restoreOverrideCursor()
函数要和前面的setOverrideCursor()
函数配合使用。void Widget::mouseDoubleClickEvent(QMouseEvent *event) // 鼠标双击事件
{
if(event->button() == Qt::LeftButton){ // 如果是鼠标左键按下
if(windowState() != Qt::WindowFullScreen) // 如果现在不是全屏
setWindowState(Qt::WindowFullScreen); // 将窗口设置为全屏
else setWindowState(Qt::WindowNoState); // 否则恢复以前的大小
}
}
setWidowState()
函数来使窗口处于全屏状态或者恢复以前的大小。void Widget::wheelEvent(QWheelEvent *event) // 滚轮事件
{
if(event->delta() > 0){ // 当滚轮远离使用者时
ui->textEdit->zoomIn(); // 进行放大
}else{ // 当滚轮向使用者方向旋转时
ui->textEdit->zoomOut(); // 进行缩小
}
}
QWheelEvent类的delta()
函数获取了滚轮移动的距离,每当滚轮旋转一下,默认的是15度,这时delta()
函数就会返回15*8即整数120。当滚轮向远离使用者的方向旋转时,返回正值;当向着靠近使用者的方向旋转时,返回负值。这样便可以利用这个函数的返回值来判断滚轮的移动方向,从而进行编辑器中内容的放大或者缩小操作。QKeyEvent
类用来描述一个键盘事件。当键盘按键被按下或者被释放时,键盘事件便会被发送给拥有键盘输入焦点的部件。void Widget::keyPressEvent(QKeyEvent *event) // 键盘按下事件
{
if(event->modifiers() == Qt::ControlModifier){ // 是否按下Ctrl键
if(event->key() == Qt::Key_M) // 是否按下M键
setWindowState(Qt::WindowMaximized); // 窗口最大化
}
else QWidget::keyPressEvent(event);
}
QTimerEvent类
用来描述一个定时器事件。对于一个QObject
的子类,只需要使用int QObject::startTimer ( int interval )
函数来开启一个定时器,这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定的时间,它返回一个整型编号ID来代表这个定时器。当定时器溢出时就可以在timerEvent()
函数中获取该定时器的编号来进行相关操作。QTimer
类来实现一个定时器,它提供了更高层次的编程接口,比如可以使用信号和槽(事件和信号相联系,对应的定时器事件往往是槽函数执行的内容)
,还可以设置只运行一次的定时器。QTimerEvent
的timerId()
函数来获取定时器的编号,然后判断是哪一个定时器并分别进行不同的操作。//在主窗口中开启三个定时器
id1 = startTimer(1000); // 开启一个1秒定时器,返回其ID
id2 = startTimer(2000);
id3 = startTimer(3000);
//定时器事件函数定义
void Widget::timerEvent(QTimerEvent *event)
{
if (event->timerId() == id1) { // 判断是哪个定时器
qDebug() << "timer1";
}
else if (event->timerId() == id2) {
qDebug() << "timer2";
}
else {
qDebug() << "timer3";
}
}
QTimeEvent
类使用信号和槽函数来实现定时器在主窗口中:
QTimer *timer = new QTimer(this); // 创建一个新的定时器
// 关联定时器的溢出信号到槽上
connect(timer,SIGNAL(timeout()),this,SLOT(timerUpdate())); //定时器产生溢出事件后执行槽函数
timer->start(1000); // 设置溢出时间为1秒,并启动定时器
QTimer::singleShot(10000, this, &Widget::close); // 10s 之后窗口会关闭
// 溢出槽函数
void Widget::timerUpdate() // 定时器溢出处理
{
QTime time = QTime::currentTime(); // 获取当前时间
QString text = time.toString("hh:mm"); // 转换为字符串
if((time.second() % 2) == 0) text[2]=' '; // 每隔一秒就将“:”显示为空格
ui->lcdNumber->display(text);
}
timeout()
信号,这时就会执行我们的定时器溢出处理函数。在槽里我们获取了当前的时间,并且将它转换为可以显示的字符串。rand()
和qsrand()
两个函数实现的。// 在widget构造函数中添加以下代码:
qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime())); //初始化随机数
// 在定时器存储函数中添加:
int rand = qrand() % 300; // 产生300以内的正整数
ui->lcdNumber->move(rand, rand); //ui界面中lcdNumber部件每隔一秒就移动到一个随机位置
qrand()
函数产生随机数之前,一般要使用qsrand()
函数为其设置初值,如果不设置初值,那么每次运行程序,qrand()
都会产生相同的一组随机数。为了每次运行程序时,都可以产生不同的随机数,我们要使用qsrand()
设置一个不同的初值。这里使用了QTime
类的secsTo()
函数,它表示两个时间点之间所包含的秒数,比如代码中就是指从零点整到当前时间所经过的秒数。当使用qrand()
要获取一个范围内的数值时,一般是让它与一个整数取余,比如这里与300取余,就会使所有生成的数值在0-299之间(包含0和299)。######处理部件的事件往往有重新建立一个新类继承该部件的基类,之后重新写该新类的事件处理函数,但是这样对于不同的部件都需要重新建类改写事件处理函数,效率太低,所以一般使用事件过滤器,事件过滤器可以对不同部件的不同事件进行处理,无需另外新建类,效率高。
######事件的发送利用postEvent()
和sendEvent()两种函数把定义好的
event`发送至指定的部件,之后部件执行相应的事件处理函数。这两种事件发送函数的不同之处为:
sendEvent()
会立即处理给定的事件;但是其中的QEvent
对象参数在事件完成后无法自动删除,所以需要在栈上创建QEvent
对象;
postEvent()
则会将事件放到等待调度队列中,当下一次Qt的主事件循环运行时才会处理;它的QEvent
对象需要在堆上创建(例如使用new
),当事件发送后事件队列会自动删除它。
// 在主窗口构造函数为text和spinbox两个部件添加两个事件过滤器
ui->textEdit->installEventFilter(this); // 为编辑器部件在本窗口上安装事件过滤器
ui->spinBox->installEventFilter(this);
// 新建事件进行发送
QKeyEvent myEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
qApp->sendEvent(ui->spinBox, &myEvent); // 发送键盘事件到spinBox部件,该事件处理函数为Spinbox默认,spinbox的值加1,如果没有发送这个事件,spinbox中的默认值为0
// 事件过滤器中针对不同部件事件的执行函数
bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器
{
if (obj == ui->textEdit) { // 判断部件
if (event->type() == QEvent::Wheel) { // 判断事件
// 将event强制转换为发生的事件的类型
QWheelEvent *wheelEvent = static_cast(event); // 将发生的事件强制转换为鼠标滚轮事件以便后续判断操作
if (wheelEvent->delta() > 0) ui->textEdit->zoomIn(); //利用鼠标滚轮的delta属性来对文本放大或者缩小
else ui->textEdit->zoomOut();
return true; // 该事件已经被处理
} else {
return false; // 如果是其他事件,可以进行进一步的处理
}
}
else if (obj == ui->spinBox) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast(event); //强制转换为键盘事件
if (keyEvent->key() == Qt::Key_Space) { //如果触发了空格键,spinbox的值变为0
ui->spinBox->setValue(0);
return true;
} else {
return false;
}
} else {
return false;
}
}
else return QWidget::eventFilter(obj, event); //执行QWidget基类的默认事件过滤器函数
}