最近在为bestv做一个播放的plugin,这个plugin主要是在Qt中完成play等功能并能在JavaScript里面进行调用,其中里面就遇到了信息、事件等问题。开始时,我选择通过connect关联singal和slots,再利用emit来触发信息,从而达到执行和回调信息给JavaScript的作用(之所以选择这种,其中一个主要的因素,就是设计plugin类的哥们定义了event处理方式,就是emit方式——从而,我也觉得此种方式可以完成Qt Object 和JSObject的回调等交互与信号传输)。不过,发现此类方式的singal触发时序很难控制并且bestv方在对singal接受部分,并未有完善的方案,最后,只好放弃emit、选择postevent。下面转载、整理、分享了一些来源于网络的Qt-event相关的文档。
------------------------------------------------------------------------------------
Qt中抛消息有:信号和槽、postEvent、sentEvent等机制,sentEvent只支持同步的。postEvent可以实现异步的,其机制是将消息发送到消息队列中,消息队列又会把这些消息都抛出(当然要实现该功能我们也可以用信号和槽机制,将connect函数的最后一个参数设置为Qt::QueuedConnection即可)。
言归正传,上段我们说到消息队列把消息抛出来,我们该如何去捕获该消息呢?我们只要实现父类中的event()或者customEvent()函数即可,在里面实现我们自己的处理,在此建议采用customEvent()。
以下是一个简单的实例:
#include <QWidget>
#include <QEvent>
const QEvent::Type CustomEvent_Login = (QEvent::Type)5001;//建议用5000以上唯一的标识
class PostEvent : public QWidget
{
Q_OBJECT
public:
PostEvent(QWidget *parent = 0);
~PostEvent();
private:
void customEvent(QEvent *e); //该函数是父类QWidget的虚函数
};
PostEvent::PostEvent(QWidget *parent)
{
QApplication::postEvent(this, new QEvent(CustomEvent_Login)); //该函数实现将自定义的消息发送到队列,且new QEvent(CustomEvent_Login))只能动态分配,原因请看Qt的帮助文档中的postEvent函数说明。
}
void PostEvent::customEvent(QEvent *e)
{
if (e->type() == CustomEvent_Login) //捕获消息
{
QMessageBox msgBox;
msgBox.setText("The document has been modified.");
msgBox.exec();
}
}
呵呵,就这样吧 本人一开始犯了一个很白痴的错误,即把customEvent函数当作用户可以自定义的函数,殊不知是父类中的虚函数,所以一直捕获不到消息。好了,以上只是一个简单的关于postEvent的一个应用,如果想了解更多的消息机制请阅读其他关于event的文章。
/*
其实对于event的处事方式,最著名的就是两种,一是事件冒泡法(event bubbing),即:事件开始时,由event送至各事件接收方,IE浏览器的研发团队就是采用此方法;另一是事件捕获(event capturing),即事件接收方主动去捕获event。
*/
§ 键盘事件: 按键按下和松开.
§ 鼠标事件: 鼠标移动,鼠标按键的按下和松开.
§ 拖放事件: 用鼠标进行拖放.
§ 滚轮事件: 鼠标滚轮滚动.
§ 绘屏事件: 重绘屏幕的某些部分.
§ 定时事件: 定时器到时.
§ 焦点事件: 键盘焦点移动.
§ 进入和离开事件: 鼠标移入widget之内,或是移出.
§ 移动事件: widget的位置改变.
§ 大小改变事件: widget的大小改变.
§ 显示和隐藏事件: widget显示和隐藏.
§ 窗口事件: 窗口是否为当前窗口.
Qt 的事件和 Qt 中的 signal 不一样 , 后者通常用来 " 使用 "widget, 而前者用来 " 实现 " widget。 比如一个按钮 , 我们使用这个按钮的时候 , 我们只关心他 clicked() 的 signal, 至于这个按钮如何接收处理鼠标事件 , 再发射这个信号 , 我们是不用关心的 。 但是如果我们要重载一个按钮的时候 , 我们就要面对 event 了 , 比如我们可以改变它的行为 , 在鼠标按键按下的时候 (mouse press event) 就触发 clicked() 的 signal 而不是通常在释放的 ( mouse release event) 时候。如下图给出event处理过程的示意图。Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环. 该循环可以简化的描述为如下的代码:
while (!app_exit_loop ) {
while( !postedEvents ) { processPostedEvents() }
while( !qwsEvnts ){ qwsProcessEvents(); }
while( !postedEvents ) { processPostedEvents() }
}
先处理Qt事件队列中的事件, 直至为空. 再处理系统消息队列中的消息,直至为空, 在处理系统消息的时候会产生新的Qt事件, 需要对其再次进行处理.
调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的. 实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了事件的派发和处理环节。
---------------------------------------------------------------------------------
事件过滤器:
事件过滤器是Qt中一个独特的事件处理机制,功能强大而且使用起来灵活方便.通过它,可以让一个对象侦听拦截另外一个对象的事件.事件过滤器是这样实现的:在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObjec(qobjA)给另一个QObject (qobjB)安装了事件过滤器之后, qobjB会把qobjA的指针保存在eventFilters中.在qobjB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数.一个对象可以给多个对象安装过滤器.同样,一个对象能同时被安装多个过滤器,在事件到达之后,这些过滤器以安装次序的反序被调用.事件过滤器函数( eventFilter() )返回值是bool型,如果返回true,则表示该事件已经被处理完毕, Qt将直接返回,进行下一事件的处理;如果返回false,事件将接着被送往剩下的事件过滤器或是目标对象进行处理.
---------------------------------------------------------------------------------
根据对Qt事件机制的分析, 我们可以得到5种级别的事件过滤,处理办法. 以功能从弱到强, 排列如下:
最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent()这样的特定事件处理函数. 以按键事件为例, 一个典型的处理函数如下:
void imageView::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Key_Plus:
zoomIn();
break;
case Key_Minus:
zoomOut();
break;
case Key_Left:
// …
default:
QWidget::keyPressEvent(event);
}
}
通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件.
下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上. )
bool CodeEditor::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_Tab) {
insertAtCurrentPosition('/t');
return true;
}
}
return QWidget::event(event);
}
安装事件过滤器有两个步骤:(假设要用A来监视过滤B的事件)
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.
用这种方法改写上面的例子:(假设我们将CodeEditor 放在MainWidget中)
MainWidget::MainWidget()
{
// …
CodeEditor * ce = new CodeEditor( this, “codeeditor”);
ce->installEventFilter( this );
// …
}
bool MainWidget::eventFilter( QOject * target , QEvent* event )
{
if( target== ce ){
if(event->type() == QEvent::KeyPress ) {
QKeyEvent*ke = (QKeyEvent *) event;
if(ke->key() == Key_Tab ){
ce->insertAtCurrentPosition('/t');
return true;
}
}
}
returnfalse;
}
一旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个eventFilter().在debug的时候,这个办法就非常有用, 也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉. ( 在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃)
Qt是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法.通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事件过滤器,相比之下, notify()函数只有一个.
--------------------------------------------------------------------------------