全部博文(584)
分类: WINDOWS
2011-04-06 22:29:31
2007 年 1 月 28 日
事件是由窗口系统或 Qt 本身对各种事务的反应而产生的。 当用户按下一个键或者鼠标按钮,就会相应产生一个键盘或者鼠标事件。 当窗口第一次显示,会产生一个绘图事件,从而通知最新的可见窗口绘制自身。 使用 Qt 编程时,很少需要考虑事件,因为 Qt 的窗口组件会在事件发生的时候发送信号,然后利用 Signal-Slot 机制进行相应的事件处理。 但是,在编写自定义 Widget 或者需要修改现有Widget的特性时,或者需要处理平台相关的底层事件时,事件则变得很重要。 本文以 XWindow 平台为例,介绍 Qt 如何处理平台相关的底层事件。
1. Qt 中与平台相关的宏定义
Qt 提供了以下标识系统的宏定义,如表1所示:
系统标志 | 对应的平台 |
---|---|
Q_WS_WIN | Windows |
Q_WS_X11 | XWindow |
Q_WS_MAC | Mac |
如果项目中调用了平台相关的 APIs ,那么利用条件编译,使得源程序不用修改,就可以适用于不同的平台。
对于 XWindow 平台,代码片断如清单1所示:
#if defined(Q_WS_X11) //与 XWindow 相关的 native APIs #endif |
对于 Windows 平台,代码片断如清单2所示:
#if defined(Q_WS_WIN) //与 Windows 相关的 native APIs #endif |
对于 MAC 平台,代码片断如清单3所示:
#if defined(Q_WS_MAC) //与Mac相关的native APIs #endif |
2. Xlib 与 XEvent
Xlib 是 XWindow 提供的 API 集合,Xlib 之上是 Xt Intrinsics library,Xt 采用面向对象的设计方法,提供了大量的 widgets。 在 Xt 之上则是各种各样的 toolkits,最常用的是 Motif。 Qt 没有对 Xt 进行封装,而是直接建立在 Xlib 之上。
Xlib 用一个叫"XEvent"的结构来保存从 X 服务器那里接收到的事件。 Xlib 提供了大量的事件类型。 XEvent 包括事件的类型,以及与事件相关的数据。
Qt 也定义了一组事件类。 其中 QEvent 是基类。 其他具体事件类,例如 QMouseEvent、QKeyEvent、QFocusEvent、QPaintEvent、QMoveEvent、QResizeEvent 等都是从 QEvent 继承而来。 多数情况下,利用 Qt 的事件机制,能够满足我们的需求。 但是,在某些特定的情况,我们需要能够截获 XWindow 的底层事件,并对其进行处理。 本文介绍了两种截获 XEvent 的方法,一种是重载 QApplication 的 x11EventFilter() 方法;另一种是重载 QWidget 的 x11Event() 方法。 文中提供的例子基于 Qt 4.3,并在 Fedora Core 6 上调试通过。
3. QApplication::x11EventFilter() 如何工作
QApplication 类管理 Qt 应用程序的控制流和主要设置。 它包含了主事件循环,对来自窗口系统的所有事件进行处理和调度。 它也处理应用程序的初始化和结束,并且提供对话管理。
QApplication 中定义了针对 XWindow 平台的虚函数,如清单4所示:
bool QApplication::x11EventFilter ( XEvent * ) |
创建 QApplication 的子类,并且重新实现函数 x11EventFilter(),那么所有底层的 XEvent 会首先被函数 x11EventFilter() 截获。 如果希望在函数 x11EventFilter() 中针对某事件进行响应,那么在响应结束后返回 TRUE,表示该事件不会被分派到 QApplication 的 Qt 主事件循环中。 如果函数返回 FALSE,那么该事件会被继续分派到 QApplication 的 Qt 主事件循环中,由 Qt 包装成 Qt 事件。
此外,针对 Windows 平台,QApplication 定义了类似的虚函数,如清单5所示:
bool QCoreApplication::winEventFilter ( MSG * msg, long * result ) |
针对 Mac 平台,QApplication 定义了类似的虚函数,如清单6所示:
bool QApplication::macEventFilter ( EventHandlerCallRef caller, EventRef event ) |
下面通过一个例子说明,如何在 Qt 中处理 XEvent。 该例子包括两个可执行程序,一个是 xclient,另一个是 qtx11filter。 xclient 基于 Xlib,向 qtx11filter 发送 XClientMessageEvent。 qtx11filter 截获所有的 XEvent,但只对 XClientMessageEvent 进行处理,将其它所有类型的 XEvent 都分派到 QApplication 的 Qt 主事件循环中。
3.1 xclient 的主要流程和源代码
xclient 的主要流程如下:
xclient 的完整源代码 xclient.c 如清单7所示:
#include #include #include char line[80]; unsigned long winID, mask; XEvent ev; Display *disp; Status status; int main(int argc, char *argv[]){ disp = XOpenDisplay(NULL); if(!disp){ perror("Unable to open X display"); exit(1); } do{ printf("Enter a line: "); fgets(line, 80, stdin); if(line[0] != 'q'){ winID = strtol(line, NULL, 16); printf("You entered %d:0x%x\n", winID, winID); ev.xclient.type = ClientMessage; ev.xclient.window = winID; ev.xclient.message_type = 0; ev.xclient.format = 8; ev.xclient.data.b[0] = '2'; ev.xclient.data.b[1] = '0'; ev.xclient.data.b[2] = '0'; ev.xclient.data.b[3] = '8'; ev.xclient.data.b[4] = '\0'; mask = 0l; status = XSendEvent(disp, winID, False, mask, &ev); printf("The XSendEvent returned: %d\n", status); XFlush(disp); } }while(line[0] != 'q'); } |
在 Fedora Core 6 中,利用如下命令编译 xclient:
gcc xclient.c –o xclient –lX11 |
3.2 qtx11filter 的主要流程和源代码
qtx11filter 的主要流程如下:
qtx11filter 的完整源代码 qtx11filter.c 如清单9所示:
#include #include #include #include #include class App: public QApplication { public: App(int argc, char **argv): QApplication(argc, argv) { } #if defined(Q_WS_X11) bool x11EventFilter(XEvent *xe) { switch (xe->type) { case ClientMessage: printf("Caught ClientMessage XEvent from Window %d \n", xe->xclient.window); printf("Receive message: %s\n", xe->xclient.data.s); return true; } return false; } #endif }; class Dialog: public QDialog { public: Dialog(QWidget *parent = 0): QDialog(parent) { QPushButton *done = new QPushButton("Done", this); connect(done, SIGNAL(clicked()), qApp, SLOT(quit())); } }; int main(int argc, char **argv) { App app(argc, argv); Dialog dialog; app.setMainWidget(&dialog); dialog.show(); return app.exec(); } |
在 Fedora Core 6 中,用以下命令编译 qtx11filter:
g++ qtx11filter.c -L/usr/lib/qt-3.3/lib -I/usr/lib/qt-3.3/include -lqt-mt –lX11 -o qtx11filter |
3.3 演示 xclient 和 qtx11filter
1. 在终端启动 qtx11filter
./qtx11filter |
弹出仅包含一个按钮的窗口,如图1所示:
2. 在终端启动 xwininfo,如图2所示:
3. 将光标移动到窗口 qtx11filter,点击鼠标左键,xwininfo 可以获取 qtx11filter 的 window ID,如图3所示(截图中已经用红色方框标出)。
4. 在终端启动 xclient,并输入 qtx11filter 的 window ID,如图4所示。 然后,xclient 会向 qtx11filter 发送 XClientMessageEvent,该事件携带一个四字节的字符串"2008"。
5.打开运行 qtx11filter 的终端,qtx11filter 已经从 window ID 为 39845889 的窗口接收到一个 XClientMessageEvent,并读取出该事件所携带的数据"2008",如图5所示:
4. QWidget::x11Event() 如何工作
QWidget 是 Qt 所有用户界面对象的基类。 它从窗口系统接收鼠标、键盘和其它事件,并且在屏幕上绘制自己。
QWidget 定义了针对 XWindow 平台的虚函数:
bool QWidget::x11Event ( XEvent * ) |
创建 QWidget 或者其已有派生类的子类,并且重新实现函数 x11Event(),那么所有发送到该 Widget 的 XEvent 首先被函数 x11Event() 截获。 如果希望在函数 x11Event() 中针对某事件进行响应,那么在响应结束后返回 TRUE。 如果函数返回 FALSE,那么该事件会被包装成 Qt 事件,发送给 Widget。
QWidget::x11Event(XEvent *) 和 QApplication::x11EventFilter ( XEvent * ) 的区别在于:前者在 XEvent 发送到特定 Qt Widget 之前,被该 Widget 截获;后者在 XEvent 发送到整个程序的主事件循环之前,被 QApplication 截获。
5. Qt 中其它与平台相关的 APIs
在每个平台上,Qt 都为 QWidget 提供了一个 winId() 函数,返回该 Qt Widget 的 window ID;QWidget 还提供了一个静态函数 find(),该函数可以返回一个特定 window ID 所对应的 Qt Widget。 我们可以将获得的 window ID 传递给 Native API 来执行平台特定的操作。
此外,针对 XWindow,Qt 提供了类 X11Info ,用以获取 XWindow 平台的信息。 例如,通过调用静态成员函数 X11Info::display() 可以获取 Qt 程序与 Xserver 建立的连接句柄。