Qt深入浅出(十一)事件处理机制
2018年02月24日 00:34:32 吓人的猿 阅读数:283
事件处理机制
什么是事件,比如当我们打开一个窗口, 如果我们不动鼠标或者键盘, 那这个窗口就永远静静的躺着, 只有我们使用鼠标点击,或者键盘按下键的时候, 窗口才会有对应的反应。那么这个过程中就是用户向窗口发送了事件。
在 Qt 的界面应用程序都是事件驱动的,程序的每个动作也都是由某个事件所触发。在Qt 中事件抽象成QEvent类。 Qt中的事件有其对应的事件处理函数,事件先由QApplication::exec()消息循环接收,然后传递给对应窗口。
1 窗口事件处理函数event
Qt中所有的事件都是继承至QEvent, QEvent继承至QObject。每个窗口会收到各种事件,每个窗口也都有一个总的事件处理函数,用来处理事件或者进一步分派事件。
窗口的这个事件处理函数为QWidget::event()。
1
2
[virtual protected] bool QWidget::event(QEvent *event);
需要注意它是一个保护权限的虚函数,那么我们一般通过派生QWidget对象,重写这个虚函数,来自定义事件处理。
Widget.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4

include

5

include

6
class Widget : public QWidget
7
{
8
Q_OBJECT
9
10
public:
11
Widget(QWidget parent = 0);
12
bool event(QEvent
event);
13
~Widget();
14
15
};
16

endif // WIDGET_H

Widget.cpp
1
2

include “widget.h”

3

include

4

include

5
Widget::Widget(QWidget parent)
6
: QWidget(parent)
7
{
8
}
9
10
bool Widget::event(QEvent
ev)
11
{
12
/只对鼠标点击事件进行处理,其它的调用基类的event函数/
13
if(ev->type() == QEvent::MouseButtonPress ||
14
ev->type() == QEvent::MouseButtonDblClick)
15
{
16
qDebug() << FUNCTION << endl;
17
//event->ignore(); //代表我无视这个消息
18
event->accept(); //代表我接收了这个消息
19
//return false; //这个消息我没有处理.
20
return true; //这个消息在我已经处理过了
21
22
}
23
return QWidget::event(ev);
24
}
25
26
Widget::~Widget()
27
{
28
29
}
需要注意的是:只有event->accept()以及return true时,才不往父窗口传递,否则都会传递。
2 鼠标事件
2.1 鼠标点击事件
Qt中的鼠标事件为QMouseEvent, 鼠标按下事件对应的事件处理函数为:
1
2
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
这个是QWidget里面实现的鼠标按下事件处理函数, 它也是保护类型的虚函数,那么,我们一般是去覆写这个虚函数来实现我们想要的处理函数.
示例代码:
Widget.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4
5

include

6

include

7
class Widget : public QWidget
8
{
9
Q_OBJECT
10
11
public:
12
Widget(QWidget parent = 0);
13
void mousePressEvent(QMouseEvent
); //重写虚函数
14
~Widget();
15
16
};
17
18

endif // WIDGET_H

Widget.cpp
1
2

include “widget.h”

3

include

4

include

5
Widget::Widget(QWidget parent)
6
: QWidget(parent)
7
{
8
}
9
10
void Widget::mousePressEvent(QMouseEvent
ev)
11
{
12
qDebug() << FUNCTION << endl;
13
if(ev->button() == Qt::LeftButton)
14
{
15
qDebug() << “left button press” << endl;
16
}
17
else if(ev->button() == Qt::RightButton)
18
{
19
qDebug() << “right button press” << endl;
20
}
21
else if(ev->button() == Qt::MidButton)
22
{
23
qDebug() << “mid button press” << endl;
24
}
25
qDebug() << ev->pos() << endl;
26
}
普通鼠标有左右中三个键,可通过QMouseEvent::button来判断是哪个键:
1
2
Qt::MouseButton QMouseEvent::button() const;
3
/
4
返回值为枚举类型:Qt::LeftButton 左键、Qt::RightButton 右键、Qt::MidButton 中键、Qt::NoButton没有键按下。
5
/
鼠标的点击位置可以通过QMouseEvent::pos来获取:
1
2
QPoint QMouseEvent::pos() const
2.2 鼠标释放事件
Qt中的鼠标释放事件也是一个QMouseEvent对象, 鼠标按下事件对应的事件处理函数为:
1
2
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent
event);
使用方式,与鼠标点击事件类似,不再复述。
2.3 鼠标移动事件
Qt中的鼠标移动事件为也是一个QMouseEvent对象, 鼠标移动事件对应的事件处理函数为:
1
2
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent event);
默认情况下, 鼠标移动事件触发条件必须是鼠标至少一个按键按下状态, 再去移动鼠标,但是鼠标QMouseEvent::Button()还是Qt::NoButton。
1
2
void Widget::mouseMoveEvent(QMouseEvent
ev)
3
{
4
qDebug() << ev->pos() << endl;
5
qDebug() << ev->button() << endl; //不能用这个判断到底是哪个键按下
6
}
如果想要不按下鼠标按键,就触发鼠标移动事件可以调用QWidget::setMouseTracking
1
2
void QWidget::setMouseTracking(bool enable);
你可以参数在Widget中构造函数中调用该函数,enable参数为true代表开启鼠标跟踪,那么鼠标不需要按下任何键都能触发鼠标移动事件。
2.4 鼠标滚轮事件
鼠标滚轮事件为QWheelEvent, 继承自QObject,对应的事件处理函数为:
1
2
[virtual protected] void QWidget::wheelEvent(QWheelEvent event);
例如:
1
2
void Widget::wheelEvent(QWheelEvent
ev)
3
{
4
qDebug() << ev->angleDelta() << endl;
5
}
2.5 其它鼠标事件
鼠标双击事件对应的事件处理函数
1
2
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent event)
鼠标进入窗口对应的事件处理函数
1
2
[virtual protected] void QWidget::enterEvent(QEvent
event)
鼠标离开窗口对应的事件处理函数
1
2
[virtual protected] void QWidget::leaveEvent(QEvent event)
3 键盘事件
在Qt中键盘事件是QKeyEvent,继承自QObject。
3.1 键盘按下事件
按下的事件对应的处理函数为:
1
2
[virtual protected] void QWidget::keyPressEvent(QKeyEvent
event)
例如:
widget.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4

include

5
class Widget : public QWidget
6
{
7
Q_OBJECT
8
9
public:
10
Widget(QWidget parent = 0);
11
~Widget();
12
void keyPressEvent(QKeyEvent
ev);
13
};
14
15

endif // WIDGET_H

16
widget.cpp
1
2

include “widget.h”

3

include

4

include

5
Widget::Widget(QWidget parent)
6
: QWidget(parent)
7
{
8
9
}
10
void Widget::keyPressEvent(QKeyEvent
ev)
11
{
12
switch(ev->key())
13
{
14
case Qt::Key_A:
15
qDebug() << “key a pressed” << endl;
16
break;
17
case Qt::Key_B:
18
qDebug() << “key b pressed” << endl;
19
break;
20
default:
21
break;
22
}
23
}
通过QKeyEvent::key()函数获取到具体按下的某个键值:
1
2
int QKeyEvent::key() const;
3
/*
4

  • 返回值的具体键值可以查看Qt帮助文档
    5
    /
    3.2 键盘释放事件
    鼠标按下事件对应的事件处理函数为:
    1
    2
    [virtual protected] void QWidget::keyReleaseEvent(QKeyEvent
    event);
    使用方法与键盘按下事件类似,不再复述。
    3.3 设置焦点窗口

我们知道键盘事件是发送给当前激活状态的窗口,当只有一个窗口的时候,键盘事件定然是传递给这个窗口的。但是如果这个窗口上面有多个子窗口的时候,我们需要指定一个焦点窗口用来接收键盘事件。
焦点窗口可以通过tab键、鼠标按键点击等方式切换,也可以通过代码的方式来切换:
1
2
void QWidget::setFocus(Qt::FocusReason reason);
3
/
4
Qt::FocusReason reason 告诉Qt焦点切换的原因,一般填个Qt::OtherFocusReason即可
5
/
3.4 判断按键大小写
通过QKeyEvent的text()函数:
1
2
QString QKeyEvent::text() const
3
/

4
返回一个QString类型,这个是键盘产生的字符串
5
/
那么我们可以这样判断大小写:
1
2
void Widget::keyPressEvent(QKeyEvent e)
3
{
4
if( e->text() >= “A” && e->text() <= “Z” )
5
{
6
qDebug() << “uppercase” << endl;
7
}
8
else if( e->text() >= “a” && e->text() <= “z” )
9
{
10
qDebug() << “lowcase” << endl;
11
}
12
}
4 事件过滤器
上面我们看到,事件都只能在自己类中处理,如果其它对象想知道某个窗口的事件,可以通过给窗口安装事件过滤器,对应的函数为:
1
2
void QObject::installEventFilter(QObject
filterObj);
3
/
4
这个函数谁调用,谁被监控
5
_QObject _filterObj 监控者
6
/
事件过滤器的处理函数为:
1
2
[virtual] bool QObject::eventFilter(QObject _watched, QEvent _event);
3
/

4

  • QObject *watched,被监控对象
    5
  • QEvent *event 具体哪些事件
    6
  • 返回值 true 代表过滤事件, false 代表不过滤
    7
    */
    例如:
    Widget.h
    1
    2
    Widget.h
    3

    ifndef WIDGET_H

    4

    define WIDGET_H

    5
    6

    include

    7
    8
    class Widget : public QWidget
    9
    {
    10
    Q_OBJECT
    11
    12
    public:
    13
    Widget(QWidget *parent = 0);
    14
    ~Widget();
    15
    bool eventFilter(QObject _watched, QEvent _event);
    16
    17
    };
    18
    19

    endif // WIDGET_H

    Widget.c
    1
    2

    include “widget.h”

    3

    include

    4

    include

    5

    include

    6

    include

    7

    include

    8
    9
    Widget::Widget(QWidget parent) : QWidget(parent)
    10
    {
    11
    this->resize(400, 300);
    12
    QHBoxLayout
    hBox = new QHBoxLayout;
    13
    MyButton* pb = new MyButton(“pb”);
    14
    hBox->addWidget(pb);
    15
    this->setLayout(hBox);
    16
    pb->installEventFilter(this); //给pb对象安装事件过滤器
    17
    }
    18
    19
    bool Widget::eventFilter(QObject _watched, QEvent _event)
    20
    {
    21
    if(event->type() == QEvent::MouseButtonPress
    22
    1. || event->type() == QEvent::MouseButtonDblClick)<br />
    23
    {
    24
     qDebug()  <<  "eventFilter"  <<  endl;<br />
    
    25
     return  true;  //过滤掉点击事件<br />
    
    26
    }
    27
    return QWidget::eventFilter(watched, event);
    28
    }
    29
    30
    Widget::~Widget()
    31
    {
    32

33
}
mybutton.h
1
2

ifndef MYBUTTON_H

3

define MYBUTTON_H

4

include

5

include

6
class MyButton : public QPushButton
7
{
8
public:
9
MyButton(const QString & text, QWidget parent = 0);
10
void mousePressEvent(QMouseEvent
ev);
11
};
12
13

endif // MYBUTTON_H

mybutton.cpp
1
2

include “mybutton.h”

3

include

4
5
MyButton::MyButton(const QString & text, QWidget parent)
6
: QPushButton(text, parent)
7
{
8
9
}
10
void MyButton::mousePressEvent(QMouseEvent
ev)
11
{
12
static int i = 0;
13
qDebug() << FUNCTION << i++ << endl;
14
}
5 事件传递过程
QApplication的exec从桌面系统中获取事件,然后分发给对应的窗口事件处理函数bool event(QEvent),然后该函数再分发给具体的某个事件处理函数,如鼠标事件、键盘事件等。
6 事件处理函数是如何被调用的
我们重定义事件处理函数后,并没有显示的调用,那为什么,事件能够进入到我们的事件处理函数中呢?
这是其实是因为,我们的事件处理函数都是虚函数,我们override了基类的事件处理函数, 而Qt在底层实现了使用基类的指针指向我们的派生类对象, 并且在事件触发时,使用基类指针来调用我们override的事件处理函数。
简单来说,就是应用了c++多态。
7 绘图事件
在桌面系统中所有的窗口都是绘制出来的, 那么在窗口形态发生变化时, 就会产生一个绘画事件, 在Qt中叫作QPaintEvent, 其对应的事件处理函数是:
1
2
[virtual protected] void QWidget::paintEvent(QPaintEvent event);
7.1 QPainter画家
我们知道画图一般需要一个画家来画,画家想要画图首先要给它一张画纸,然后画家需要一把画笔来画轮廓, 一把笔刷来填充颜色。
那么在Qt中我们的画家叫做QPainter, 画纸可以是任何QPaintDevice的派生类对象,
QWidget本身也继承至QPaintDevice,所以也能用来当作画纸。继承至QPaintDevice的类还有QPixmap、QPicture、QImage。
QPainter有QPen画笔, QBrush笔刷。
注意:当画纸为QWidget的派生类对象时, QPainter只能在QWidget的painteEvent事件处理函数中使用.
*7.2 在窗口上绘图

设置画笔笔刷, 并且绘制出线,文本,矩形等等:
widet.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4
5

include

6
7
class Widget : public QWidget
8
{
9
Q_OBJECT
10
11
public:
12
Widget(QWidget parent = 0);
13
~Widget();
14
void paintEvent(QPaintEvent
event);
15
16
};
17
18

endif // WIDGET_H

widget.cpp
1
2

include “widget.h”

3

include

4

include

5

include

6
7
Widget::Widget(QWidget parent) : QWidget(parent)
8
{
9
this->resize(400, 300);
10
11
QFont font = this->font(); //这个可以用来设置字体的样式
12
font.setPixelSize(10);
13
this->setFont(font);
14
}
15
16
void Widget::paintEvent(QPaintEvent
event)
17
{
18
qDebug() << “paintEvent” << endl;
19
QPainter painter;
20
painter.begin(this);
21
painter.setPen(Qt::red);
22
// painter.setPen(QPen(QBrush(Qt::red), 20, Qt::DashLine)); //画笔的高级设置
23
painter.setBrush(Qt::blue); //设置
24
// painter.setBrush(QBrush(Qt::blue, Qt::Dense3Pattern); //笔刷的高级设置
25
painter.drawText(100, 100, “huangwl”); //画字
26
painter.drawLine(0, 0, 100, 100); //画线
27
painter.drawRect(100, 100, 200, 200); //画矩形
28
painter.drawEllipse(100, 0, 100, 200); //画椭圆
29
painter.end();
30
}
31
32
Widget::~Widget()
33
{
34
35
}
7.3 转变窗口坐标系
更改绘图坐标系的坐标原点
1
2
void QPainter::translate(constQPointF &offset)
更改绘图坐标系的角度
1
2
void QPainter::rotate(qreal angle)
缩放坐标系单位
1
2
void QPainter::scale(qreal sx, qreal sy)
Widget.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4
5

include

6
7
class Widget : public QWidget
8
{
9
Q_OBJECT
10
11
public:
12
Widget(QWidget parent = 0);
13
~Widget();
14
void paintEvent(QPaintEvent
event);
15
16
};
17
18

endif // WIDGET_H

Widget.cpp
1
2

include “widget.h”

3

include

4

include

5

include

6
7
Widget::Widget(QWidget parent) : QWidget(parent)
8
{
9
this->resize(600, 480);
10
}
11
12
void Widget::paintEvent(QPaintEvent
event)
13
{
14
qDebug() << “paintEvent” << endl;
15
QPainter painter;
16
painter.begin(this);
17
painter.setPen(Qt::red);
18
painter.setBrush(Qt::blue);
19
painter.translate(100, 0); //坐标原点转换到(100,0)位置
20
painter.rotate(45); //顺时针旋转45度
21
painter.scale(0.5, 0.5); //坐标系的单位变为原来的0.5
22
painter.drawRect(0, 0, 100, 100); //画矩形
23
painter.end();
24
}
25
26
Widget::~Widget()
27
{
28
29
}
7.4 保存与还原QPainter设置
保存之前QPainter所有的设置
1
2
void QPainter::save()
还原之前QPainter的设置.
1
2
void QPainter::restore()
7.5 绘制事件触发的时机
窗口失去焦点, 窗口缩放.
窗口被覆盖, 或者窗口被显示出来
在代码中可以用函数repaint()和update()
repaint() 立即重绘,并且可以指定重绘区域
1
2
[slot] void QWidget::repaint()
update() 不是马上重绘,加入到消息队列中,由消息循环来调度,有时多个update()会合并成一个重绘事件。
1
2
[slot] void QWidget::update()
例如:
widget.h
1
2

ifndef WIDGET_H

3

define WIDGET_H

4
5

include

6
7
class Widget : public QWidget
8
{
9
Q_OBJECT
10
11
public:
12
Widget(QWidget parent = 0);
13
~Widget();
14
void paintEvent(QPaintEvent
event);
15
public slots:
16
void doRepaint();
17
void doUpdate();
18
private:
19
QColor _color;
20
};
21
22

endif // WIDGET_H

Widget.cpp
1
2

include “widget.h”

3

include

4

include

5

include

6

include

7

include

8

include

9
10
Widget::Widget(QWidget parent) : QWidget(parent), _color(Qt::red)
11
{
12
QDialog
dialog = new QDialog(this);
13
QPushButton pb0 = new QPushButton(“update”, this);
14
QPushButton
pb1 = new QPushButton(“repaint”, this);
15
QHBoxLayout hBox = new QHBoxLayout(this);
16
hBox->addWidget(pb0);
17
hBox->addWidget(pb1);
18
dialog->setLayout(hBox);
19
20
this->resize(600, 480);
21
dialog->resize(400, 300);
22
dialog->show();
23
24
connect(pb0, SIGNAL(clicked()), this, SLOT(doUpdate()));
25
connect(pb1, SIGNAL(clicked()), this, SLOT(doRepaint()));
26
}
27
void Widget::doUpdate()
28
{
29
_color.setRgb(0, 0, 255);
30
this->update();
31
}
32
33
void Widget::doRepaint()
34
{
35
_color.setRgb(0, 255, 0);
36
this->repaint(100, 100, 200, 200);
37
}
38
39
void Widget::paintEvent(QPaintEvent
event)
40
{
41
QPainter painter;
42
painter.begin(this);
43
painter.setBrush(_color);
44
painter.drawRect(0, 0, this->width(), this->height()); //画矩形
45
painter.end();
46
}
47
48
Widget::~Widget()
49
{
50
已使用 Microsoft OneNote 2016 创建。