在前面的章节里简单的介绍了Qt的几个事件,这里介绍一个和事件相关的东西:事件过滤器,这个。。。。。暂且称为“东西”的作用就是过滤事件,简单的来说,假如一个对话框(QDialog)上面放了10个QPushButton,当鼠标点击其中一个按钮的时候就触发了按钮的鼠标点击事件,这个事件并不是由按钮直接产生,Qt对此的策略是先把鼠标点击发送给父窗体(在这个例子里是QDialog),然后再由QDialog发送给他的子窗体(这个例子是10个QPushButton其中的某个),事件过滤器的作用就是在事件由父窗体传发送到子窗体过程中设置一道“网”,使的我们能够确切的知道具体的事件发送给某个具体的窗体,这样我们可以在事件发送给对象之前就能知道,同时也使得我们有能力决定是否允许父窗体把鼠标点击事件(或者其他事件)发送给子窗体。这两个功能在某些情况下回非常有用
这个程序很简单。10个按钮,点击其中的任何一个,下面的编辑框上就会显示这个按钮的文字,而要实现这一功能,关键在于需要知道鼠标究竟点击到了哪个按钮,按照以前的做法大概是这样一个流程
1 子类化QPushButton,添加一个信号thisButtonText(QString);,同时添加一个槽,isSelected();
2 把槽isSelected()和信号clicked()连接起来,而槽isSelected()里有这样一行代码
emit thisButtonText(text());
3把每个按钮的信号 thisButtonText(QString)和编辑框QTextEdit的槽setText(QString)连接起来
或者
重写鼠标点击事件,添加类似下面的代码
if(event->button() == Qt::LeftButton)
emit thisButtonText(text());
这样的做法显得繁琐,首先需要子类化QPushButton,然后修改添加自定义的信号与槽,并进行正确的连接,当然就这个例子来说增加的工作量似乎不大,但这个例子只是一个没有什么意义的,单纯演示的例子,为了更好的说明,在看一个稍微有点实际意义的程序
这个程序上面放了8个不用的窗体,点击其中的一个,下面就会出现这个窗体的简单说明,比如这个窗体有什么作用,有哪些接口等等,这个程序可以向用户演示窗体的作用而不需要去查文档。每个公司都会自定义一些类,把这些自定义的类放到这个程序上面(点击他们可以出现说明),这对于公司的新员工很有帮助,这可以让那些新员工快速的了解公司已有的产品(类);
要完成这样的一个程序,主要思路上面已经演示过了,但问题这样的方式需要子类化每个按钮,同时添加信号与槽,以这个程序为例,上面8个不同的窗体就需要子类化8个,光文件就多了8X2个(.h和.cpp文件各一),而且这样做还有一个很明显的不足之处,如果上面放的是某些自定义的类,这些类可能处于某些功能上的需要不发射clicked(),换言之,有些自定义类设计为没有clicked()信号,当然,你也可以去修改那些(不是你写的)类的源代码,通过修改源代码来让这写自定义类重新发射信号,但你很难确定让类重新具有发射Clicked()信号的能力会不会有其他影响(也许根本无法通过编译),所以只能重新看所有完源代码来确保类重新具有clicked()信号没有问题,至少能通过编译,运行,当然一个类源代码如果有上万行甚至更多也并不稀奇。。。。。。
之所以会出现上面的这些问题,根本原因在于处理对于一个鼠标点击按钮的事件,以鼠标点击事件为例,当我们点击了上面的任何一个窗体时,Qt的策略是事件发给主程序,主程序在发送给对应的窗体,而我们前面做的是在鼠标点击事件发送给窗体后再做处理,要解决这个问题就需要用到事件过滤器,事件过滤器有两个非常强的功能
1 事件过滤器能够判断事件的种类和对象,以这个“窗体演示“程序为例,当点击程序上任何一个窗体,通过事件过滤器可以判断出事件的类型(用户是点击了鼠标还是按了键盘,或者其他,这些事件可能是用户触发的,也可能是其他程序触发的事件),也可以判断出事件的对象是哪个(既用户究竟点了哪个窗体)
2 事件过滤器在获得这个事件是,可以决定是否将这个事件发送给对象,换句话说,Qt将事件发送给父窗体,父窗体并不是直接发送给子窗体,而是发送给事件过滤器,由事件过滤器决定是否把这个事件发送给子窗体
说了这么多,那事件过滤器到底是什么样的呢?其实他很简单,就一个函数而已
bool eventFilter(QObject* obj , QEvent* event);
这个函数有两个参数,一个“对象”,一个“事件”,注意这个函数的返回值是布尔值,这个函数决定是否把参数“事件”发送给另一个参数“对象”,如果这个函数返回false,则event会被发送给obj,而返回true表示事件被拦截,不会被发给obj了,而上面的“窗体演示”程序,是每次点击一窗体就显示这个窗体的文字说明,基本思路是核对这两个参数,如果obj是需要说明的窗体,event也是鼠标点击事件,那就设定QTextEdit的文本,当然是否允许点击事件发给对象需要看程序的实际需求.所以这个函数基本实现类似这样
if(obj != 目标窗体)
return 父类的函数eventFilter(obj,event)
if(event != 鼠标点击事件)
return 父类的函数eventFilter(obj,event)
doSomething
return 父类的函数eventFilter(obj,event);
上面的“伪代码”换成人话来说
1 先判断是不是目标窗体(这个程序里是那8个窗体,主窗体和QTextedit都不是),如果不是不做任何事,调用父类函数后退出
2 判断是不是目标事件,这个程序里是鼠标点击事件,如果不是不做任何是,然后调用父类函数后退出
3 经过1,2的判断可以确定我们获得的是正确的窗体,正确的事件,然后doSomething这里是设置QTextEdit的内容(即窗体的说明)
4 doSomething完成后调用父类函数执行原本的其他功能并退出(类似事件处理中调用父类的事件函数)
最后我们看这个函数的实现代码
bool EventFilterExample::eventFilter(QObject* obj, QEvent* event)
{
if(event->type() != QEvent::MouseButtonPress) //注释1
return QDialog::eventFilter(obj,event);
if(static_cast<QMouseEvent*>(event)->button() != Qt::LeftButton) //注释2
return QDialog::eventFilter(obj,event);
if(obj == example_PushButton) //注释3
instruction_TextEdit->setText(tr("This Is A PushButton......"));
else if(obj == example_Label)
instruction_TextEdit->setText(tr("This Is A Label......"));
else if(obj == example_RadioButton)
instruction_TextEdit->setText(tr("This Is A RadioButton......"));
else if(obj == example_CheckBox)
instruction_TextEdit->setText(tr("This Is A CheckBox......"));
else if(obj == example_ToolButton)
instruction_TextEdit->setText(tr("This Is A ToolButton......"));
else if(obj == example_Slider)
instruction_TextEdit->setText(tr("This Is A Slider......"));
else if(obj == example_DateTimeEdit)
instruction_TextEdit->setText(tr("This Is A DateTimeEdit......"));
else if(obj == example_ScrollBar)
instruction_TextEdit->setText(tr("This Is A ScrollBar......"));
else
return QDialog::eventFilter(obj,event); //注释4
QDialog::eventFilter(obj,event); //注释5
}
注释1 QEvent类的成员函数type()返回事件类型,通过这个函数判断这个事件是不是我们要的鼠标点击事件
注释2 由于我们只需要鼠标左键点击事件,所以这里还需要判断下是否是左键点击,这里通过static_cast<>将QEvent强制转换成QMouseEvent,然后调用QMouseEvent的的成员函数button()来判断鼠标点击按钮类型
注释3 判断是否是目标窗体,函数代码到这里已经确定了是需要的鼠标点击事件(如果不是已经return了),如果是目标窗体,就设置QTextEdit的文本(即窗体说明)
注释4 经过一串的对比,发现不是目标窗体,直接调用父类函数并返回
注释5 其实这段代码可以不用写,然后在上面每个判断if()语句里都加上这一句就可以了,即判断为目标窗体(前面的代码已经判断了是目标事件),设置文本后调用父类函数,但这样需要写8行,所以就这样写了
前面说过,主窗体把事件发送给子窗体,而过事件滤器就在中间过滤,类似这样
父窗体<------------->事件过滤器<-------------->子窗体
而函数eventFilter()可以看做连接父窗体和事件过滤器,而要让这个事件过滤器正常的工作,还需要把事件过滤器和子窗体连接起来,不过这一步就简单多了,只需要在父窗体的构造函数里(记得前面说过,事件过滤器安装在父窗体上)有example_PushButton->installEventFilter(this);
example_Label->installEventFilter(this);
example_RadioButton->installEventFilter(this);
example_CheckBox->installEventFilter(this);
example_ToolButton->installEventFilter(this);
example_Slider->installEventFilter(this);
example_DateTimeEdit->installEventFilter(this);
example_ScrollBar->installEventFilter(this);
函数installEventFilter()用于“连接到”事件过滤器,参数是事件过滤器安装的窗体,所以这里参数为this;
然后这个程序还有个问题,一些自定义的窗体点击后会跳出其他对话框,有些窗体点击后会导致程序崩溃,这很正常,有个类的功能是点击后弹出另一个自定义的对话框,而这个程序并没有那个自定义的对话框,理所当然的崩溃了。但对于我们这个程序我们只需要点击窗体显示一些信息,而这些事情在事件过滤器函数里已经做完了,换言之,其实没必要发鼠标点击事件发送给子窗体,事件过滤器的功能是过滤事件,然后发送给目标窗体,那如何让他不发送呢?记得事件过滤器的返回值吗?没错,只要返回true就行了,以“窗体演示”程序为例,假如我们希望拦截QCheckBox和QRadioButton的鼠标点击信号,我们希望点击到这连个窗体时正确的显示说明,但窗体有不会变化(这连个窗体点击后都会有变化),这里修改下eventFilter()的代码
else if(obj == example_RadioButton)
{
instruction_TextEdit->setText(tr("This Is A RadioButton......"));
return true; //注释1
}
else if(obj == example_CheckBox)
{
instruction_TextEdit->setText(tr("This Is A CheckBox......"));
return true;
}
注释1 这里返回true告诉程序,这个信号我拦截了,不要发送给目标窗体了,而如果调用父类的函数QDialog::eventFilter()则信号会发给目标窗体,父类函数返回值是false,因为事件默认是不拦截的