QObject类详述
QObject类从Qt类继承,是所有Qt对象的基类,Qt类含有全局需要的各种枚举类型、类型的定义。通常情况下,用户不需要关心这个类,因为它是QObject及很少几个类的基类。例如:它定义了枚举类型ButtonState说明了鼠标按钮的状态,它定义了枚举类型WidgetState说明了窗口部件状态等。
一、对象树
QObject类是所有Qt对象的基类,是Qt对象模型的核心。 通过QObject对象可组织成对象树,QObject类提供了对对象树进行访问需要的各种成员函数。当你创建一个对象时,这个对象的父对象自动调用函数insertChild()将这个对象插入到父对象的孩子对象链表中,并调用函数children()可得到孩子对象链表。调用函数objectTrees()可得到对象树根的所有对象的链表。调用函数queryList可查询得到符合查询条件的对象的链表。
对象链表是通过QObjectList 类实现的,QObject对象树是一个静态的QObjectList类对象object_trees,object_trees链表中存有所有的对象指针,通过object_trees链表可查询到所有的对象。QObject对象树的层次图如图11。从图中可见,QObject对象是分层管理的,顶层链表链接的是无父对象的对象,一般是顶层窗口,第二层链表是无父对象的对象的孩子链表,依此类推。object_trees对象树与文件系统的文件组织方式类似。这种树型分层结构加快了对象的查找速度。
二、事件处理过程
在Qt里,一个事件是继承自QEvent的对象。事件通过调用QObject::event()被发送到继承自 QObject 的对象。事件发送表明一个事件已经产生,用 QEvent表达这个事件,且QObject 需要做出回应。多数事件针对 QWidget和他的子类的,此外还有些不和图形相关的重要事件,比如,套接字激活,它是QSocketNotifier使用的事件。
一些事件来自窗口系统,如QMouseEvent,一些来自其他地方,如QTimerEvent,还有一些来自应用程序。Qt一视同仁,你可以象Qt自己的事件循环所作的方式一样地发送事件。
大多数事件类型有特定的类,常用的有QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。有很多别的事件类。每个类派生自QEvent且添加事件特定的函数;例如,QResizeEvent。在QResizeEvent中,就被加入了QResizeEvent::size()和QResizeEvent::oldSize()。
有些类支持多种事件类型。QMouseEvent支持鼠标移动、按压、粘滞按压、拖拽、点击、右按压等。
因为程序需要以多变和复杂的方式作出返回,因此,Qt的事件分发机制就是灵活的。一个事件被发送的通常方法是通过调用一个虚拟函数。例如:QPaintEvent通过调用QWidget::paintEvent()被分发。
(1) 创建用户事件
创建一个自定义类型的事件,你需要定义一个事件号,其值必须大于QEvent::User。为了传递有关你的自定义事件的特性。可能自定义的事件需要从QCustomEvent类继承。
示例1:用户事件类的编写
下面列出了一个用户事件类TEST_Event的示例,编写用户事件类的方法是先定义一个事件号,再实现用户事件类,应用程序将把用户事件类与Qt的事件类一样进行处理。
用户事件类TEST_Event的头文件test_event.h列出如下:
#include <qevent.h>
#include <qstring.h>
#define REFRESHUI_EVENT QEvent::User+2
class TEST_Event: public QCustomEvent
{
public:
TEST_Event();
};
用户事件类TEST_Event的实现文件test_event.cpp列出如下:
#include <test_event.h>
TEST_Event::TEST_Event():QCustomEvent(REFRESHUI_EVENT )
{
}
(2)事件发送
许多应用程序都要创建和发送他们自己的事件。这需要创建一个相应的事件类的对象,然后调用QApplication::sendEvent()或者QApplication::postEvent()发送事件。对用户事件来说,还需要用户有事件对应的操作函数。对于Qt事件来说,Qt中已实现了事件对应的操作函数。
示例2:使用用户事件类
在test_engine.cpp文件中,函数refreshUI发送事件到主窗口,要求刷新主窗口界面。在main_window.cpp文件中,主窗口类MainWindow_UI重载了customEvent函数,实现了事件需求的操作:刷新主窗口界面。
在test_engine.cpp文件中函数refreshUI实现代码列出如下:
void Engine_View::refreshUI()
{
// pmainWindowGui是应用程序主窗口类MainWindow_UI的实例
qApp->postEvent( pmainWindowGui, new TEST_Event() );//发送事件
qApp->wakeUpGuiThread();//唤醒Gui线程
}
在main_window.cpp文件中函数customEvent的实现代码列出如下:
void MainWindow_UI::customEvent( QCustomEvent * e)
{
if ( e->type() == REFRESHUI_EVENT )
{
进行主窗口刷新操作
}
}
在应用程序的基类QApplication中有事件处理函数的实现,其中,事件的发送函数说明如下:
bool QApplication::sendEvent ( QObject * receiver, QEvent * event ) [static]
sendEvent() 立即发送事件给接收对象receiver,当sendEvent()返回时,(事件过滤器和)对象已经处理过事件了。对于很多事件类,可以通过调用isAccepted()函数知道该事件能否被处理者所接受或者拒绝。
void QApplication::postEvent ( QObject * receiver, QEvent * event ) [static]
postEvent()投寄事件到一个队列,以便能延迟分发。在下次Qt的主事件循环运行时,它分发全部事件,还可进行一些优化。例如,若有数个resize事件,它们就被压缩成一个。对于paint事件同样如此:QWidget::update()调用了 postEvent(),postEvent()投寄事件可以避免屏幕因多次重画闪烁,同时还加快了运行速度。
postEvent()在对象初始化期间常常被使用,因为在对象完成初始化后,投送的消息会被很快派发。
bool QApplication::notify ( QObject * receiver, QEvent * e ) [virtual]
发送事件到接收对象者receiver,返回值是从receiver的事件处理函数中返回的值。对于某一类型的事件(如:鼠标和键事件)来说,如果接收者对事件不感兴趣(如:返回FALSE),事件将被传播到receiver的父类,如果父类不感兴趣,就一直向上级传递,直到顶层的object类。
(3)事件的处理
有5种不同的处理事件的方法,列出如下:
(1)重载函数QApplication::notify(),这可提供有效的事件控制,但仅能在派生于QApplication的类中重实现这个函数。
(2)在qApp(是QApplication的全局实例)中实现事件过滤,这样的一个事件过滤器能为所有的widget处理所有的事件,而且,可以有超过一个全局应用程序的事件过滤器。如:鼠标事件的全局事件过滤器设置了鼠标跟踪使能,则鼠标移动事件对于所有widget有效。
(3)重载QObject::event()(在QWidget类中),在任何widget特定的事件过滤器之前,QObject::event()能看到所有事件。QObject::event()函数声明列出如下:
bool QObject::event ( QEvent * e ) [virtual]
这个虚函数接收给一个对象的事件,如果事件被识别并被处理,将返回TRUE。这个函数能被用来重实现一个对象的行为。
(4)在对象上安装事件过滤器。
(5)重载Qt基类事件处理函数
当用户发现Qt基类的事件处理函数不能满足用户的需要时,可以在用户类中重载这些函数,对于特定的Qt事件,可以重载特定的事件函数,如:重载paintEvent(), mousePressEvent()等等函数。如果想对多个Qt事件处理函数进行修改,可以重载QObject::event()来实现。
示例1:重载QObject::event()
下面是重载QObject::event()函数的例子,它进行特定的tab键处理,还处理用户事件。
bool MyClass:event( QEvent * e )
{
if ( e->type() == QEvent::KeyPress )
{
QKeyEvent * ke = (QKeyEvent*) e;
if ( ke->key() == Key_Tab )
{
// 这里是特定的tab处理
k->accept();
return TRUE;
}
}
else if ( e->type() >= QEvent::User )
{
QCustomEvent * c = (QCustomEvent*) e;
// 这里是自定义事件处理
return TRUE;
}
QWidget::event( e );
}
三、事件运行机制
当应用程序的main函数中调用qApp->exec()时,应用程序进入Qt的主事件循环,Qt的主事件循环从事件队列中取出本窗口及系统事件,把它们翻译成QEvents,并使用函数QApplication::notify发送翻译的事件给相应的对象QObjects。同时,还处理控制台tty的信号、QWSserver服务器的事件,将来自socket的消息转化成事件进行分发。这些事件的处理工作在函数QEventLoop::processEvents(flags)中完成。
通常事件来自于窗口系统(用spontaneous()函数检查时返回TRUE),也可能来自使用Application::sendEvent()和QApplication::postEvent()手动发送的事件(用spontaneous()函数检查时返回FALSE)。
QObjects通过调用它们的QObject::event()接收事件,这个函数也可被子类重载来处理事件,最典型的重载是QWidget::event()。
四、事件过滤器
一个事件过滤器是一个能接收所有发送到这个对象上的事件的对象。这个过滤器能停止或转发到这个对象上的事件。事件过滤器通过eventFilter()函数来接收事件,如果这个事件应该被过滤(如:停止事件等),eventFilter()函数返回TRUE, 否则,返回FALSE。如果多个事件过滤器被安装在一个对象上,最后安装的过滤器将被激活。
QObject类还提供了事件过滤器的安装,QObject类与事件过滤相关的几个成员函数说明如下:
bool QObject::event ( QEvent * e ) [virtual] 接收到一个对象的事件,如果事件被识别并处理时,返回TRUE。这个函数能被重载来定制一个对象的行为。
bool QObject::eventFilter ( QObject * watched, QEvent * e ) [virtual] 如果一个对象上已安装了事件过滤器,eventFilter函数将被用来过滤事件。在这个函数的重载中,如果你想过滤事件e(如:让它停止不再被处理),就返回TRUE,否则返回FALSE。
void QObject::installEventFilter ( const QObject * filterObj ) 在对象filterObj上安装事件过滤器。
下面是一个事件过滤器的使用样例,MyMainWindow类在本对象上安装了事件过滤器,事件过滤处理函数eventFilter被重载用来在textEdit对象上处理KeyPress事件。没处理的事件被传递到基类的eventFilter()函数中,因为基类也可能因为内部事件处理的原因已重载了eventFilter()函数。如果你在这个函数中删除了接收者对象,确信返回TRUE,否则,Qt将向前传递事件到删除的对象中,程序将崩溃。
class MyMainWindow : public QMainWindow
{
public:
MyMainWindow( QWidget *parent = 0, const char *name = 0 );
protected:
bool eventFilter( QObject *obj, QEvent *ev );
private:
QTextEdit *textEdit;
};
MyMainWindow::MyMainWindow( QWidget *parent, const char *name )
: QMainWindow( parent, name )
{
textEdit = new QTextEdit( this );
setCentralWidget( textEdit );
textEdit->installEventFilter( this );//在本对象上安装事件过滤器
}
bool MyMainWindow::eventFilter( QObject *obj, QEvent *ev )
{
if ( obj == textEdit ) {//过滤的对象
if ( e->type() == QEvent::KeyPress ) {//过滤的事件
qDebug( "Ate key press %d", k->key() );
return TRUE;//已对事件处理,必须返回TRUE,这样,系统不会对这个事件做第二次处理了
} else {
return FALSE;
}
} else {
// 传递事件到父类
return QMainWindow::eventFilter( obj, ev );
}
}
五、定时器
使用定时器有2种方法,一种是使用QTimer类,另一种是使用QObject类的定时器。定时器的精确度依赖于操作系统和硬件,大多数平台支持20ms的精确度。
(1)QObject类的定时器
Qobject是所有Qt对象的基类,它提供了一个基本的定时器。通过QObject::startTimer(),你可以把一个以毫秒为单位的时间间隔作为参数来开始定时器。这个函数返回一个唯一的整数的定时器的标识符。这个定时器现在就会在每一个时间间隔"触发",直到你明确地使用这个定时器的标识符来调用QObject::killTimer()结束。
应用程序在main函数中通过调用QApplication::exec()来开始进行事件循环。当定时器触发时,应用程序会发送一个QTimerEvent,在事件循环中,处理器按照事件队列顺序来处理定时器事件,当处理器正忙于其它事件处理时,定时器就不能马上触发。
QObject类还提供定时器的功能,与定时器相关的成员函数有startTimer()、timerEvent()、killTimer()和killTimers()。QObject基类中的startTimer和timerEvent
原型及说明如下:
int QObject::startTimer ( int interval )
开始一个定时器并返回定时器ID,如果不能开始一个定时器将返回0。定时器开始后,每隔 interval毫秒间隔将触发一次超时事件,直到killTimer()或killTimers()被调用来删除定时器。如果interval为0,那么定时器事件每次发生时没有窗口系统事件处理。
void QObject::timerEvent ( QTimerEvent * ) [virtual protected]
虚拟函数timerEvent被重载来实现用户的超时事件处理函数。如果有多个定时器在运行,QTimerEvent::timerId()被用来查找是哪个定时器被激活。
当定时器事件发生时,虚函数timerEvent()随着QTimerEvent事件参数类一起被调用。重载这个函数可以获得定时器事件。
定时器的用法样例如下:
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject( QObject *parent = 0, const char *name = 0 );
protected:
void timerEvent( QTimerEvent * );
};
MyObject::MyObject( QObject *parent, const char *name )
: QObject( parent, name )
{
startTimer( 50 ); // 50ms定时器
startTimer( 1000 ); // 1s定时器
startTimer( 60000 ); // 1分钟定时器
}
void MyObject::timerEvent( QTimerEvent *e )//重载timerEvent函数
{
qDebug( "timer event, id %d", e->timerId() );
}
void MyObject::stopTimer()
{
killTimer( showDateTimer );
showDateTimer = -1;
}
(2)定时器类QTimer
定时器类QTimer提供当定时器触发的时候发射一个信号的定时器,QTimer提供只触发一次的超时事件。通常的使用方法如下:
QTimer * testtimer = new QTimer( this );//创建定时器
connect(testtimer, SIGNAL(timeout()),
this, SLOT(updateCaption()) );//将定时器超时信号与槽(功能函数)连接起来
testtimer ->start( 1000 ); //开始运行定时器,定时时间间隔为1000ms
testtimer定时器被作为这个窗口部件的子类,这样当这个窗口部件被删除时,定时器也会被删除。
QTimer还提供了一个简单的只有一次定时的函数singleShot。例如:一个定时器在100ms后触发处理函数animateTimeout,并且只触发一次。代码如下:
QTimer::singleShot( 100, this, SLOT(animateTimeout();