在 Qt 中处理平台相关的底层事件
级别: 中级
涂 波 (tubo@https://www.sodocs.net/doc/ce11869485.html, ), 软件开发工程师, IBM 中国软件开发中心
田 晓燕 (tianxyan@https://www.sodocs.net/doc/ce11869485.html, ), 软件开发工程师, IBM 中国软件开发中心
路 绪清 (luxuqing@https://www.sodocs.net/doc/ce11869485.html, ), 软件开发工程师, IBM 中国软件开发中心
任 谦 (renqian@https://www.sodocs.net/doc/ce11869485.html, ), 软件开发工程师, IBM 中国软件开发中心
2007 年 1 月 28 日
事件是由窗口系统或 Qt 本身对各种事务的反应而产生的。 当用户按下一个键或者鼠标按钮,就会相应产生一个键盘或者鼠标事件。 当窗口第一次显示,会产生一个绘图事件,从而通知最新的可见窗口绘制自身。 使用 Qt 编程时,很少需要考虑事件,因为 Qt 的窗口组件会在事件发生的时候发送信号,然后利用
Signal-Slot 机制进行相应的事件处理。 但是,在编写自定义 Widget 或者需要修改现有Widget 的特性时,或者需要处理平台相关的底层事件时,事件则变得很重要。 本文以 XWindow 平台为例,介绍 Qt 如何处理平台相关的底层事件。 1. Qt 中与平台相关的宏定义
Qt 提供了以下标识系统的宏定义,如表1所示:
表 1. Qt 提供的系统标识宏定义
系统标志
对应的平台
Q_WS_WIN Windows Q_WS_X11 XWindow Q_WS_MAC
Mac
如果项目中调用了平台相关的 APIs ,那么利用条件编译
,使得源程序不用修
文档选项
未显示需要
JavaScript 的文档选项
改,就可以适用于不同的平台。
对于XWindow 平台,代码片断如清单1所示:
清单 1.
#if defined(Q_WS_X11)
//与 XWindow 相关的 native APIs
#endif
对于Windows平台,代码片断如清单2所示:
清单 2.
#if defined(Q_WS_WIN)
//与Windows相关的 native APIs
#endif
对于MAC 平台,代码片断如清单3所示:
清单 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所示:
清单 4.
bool QApplication::x11EventFilter ( XEvent * )
创建QApplication 的子类,并且重新实现函数x11EventFilter(),那么所有底层的XEvent 会首先被函数x11EventFilter() 截获。如果希望在函数x11EventFilter() 中针对某事件进行响应,那么在响应结束后返回TRUE,表示该事件不会被分派到QApplication 的Qt主事件循环中。如果函数返回FALSE,那么该事件会被继续分派到QApplication 的Qt主事件循环中,由Qt包装成Qt事件。
此外,针对Windows平台,QApplication 定义了类似的虚函数,如清单5所示:
清单 5.
bool QCoreApplication::winEventFilter ( MSG * msg, long * result )
针对Mac 平台,QApplication 定义了类似的虚函数,如清单6所示:
清单 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 的主要流程如下:
1.用户通过命令行输入16进制的window ID。
2.向该window ID所在的窗口发送XClientMessageEvent。该事件所携带的数
据是一个长度为4的字符串”2008”。
xclient 的完整源代码xclient.c 如清单7所示:
清单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:
清单8.
gcc xclient.c –o xclient –lX11
3.2 qtx11filter 的主要流程和源代码
qtx11filter 的主要流程如下:
1.创建QApplication 的子类App,并实现虚函数x11EventFilter。注意使用
宏定义Q_WS_X11,因为x11EventFilter 只适用于XWindow。
2.在x11EventFilter 中,判断所截获的XEvent,如果事件类型为
ClientMessage,那么打印出该事件发送者的window ID,并且打印出该事件所携带的数据。
3.在main() 函数中,创建类App 的实例,并且通过调用app.exec(),启动
Qt主事件循环。
qtx11filter 的完整源代码qtx11filter.c 如清单9所示:
清单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:
清单10.
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
清单11.
./qtx11filter
弹出仅包含一个按钮的窗口,如图1所示:
图 1. qtx11filter 窗口
2. 在终端启动xwininfo,如图2所示:
图 2. xwininfo 窗口
3. 将光标移动到窗口qtx11filter,点击鼠标左键,xwininfo 可以获取qtx11filter 的window ID,如图3所示(截图中已经用红色方框标出)。
图 3. 获取qtx11filter 的window ID
4. 在终端启动xclient,并输入qtx11filter 的window ID,如图4所示。然后,xclient 会向qtx11filter 发送XClientMessageEvent,该事件携带一个四字节的字
符串”2008”。
图 4. 输入qtx11filter 的window ID
5.打开运行qtx11filter 的终端,qtx11filter 已经从window ID 为39845889 的窗口接收到一个XClientMessageEvent,并读取出该事件所携带的数据”2008”,如图5所示:
图 5. qtx11filter 处理XClientMessageEvent
4. QWidget::x11Event() 如何工作
QWidget 是Qt所有用户界面对象的基类。它从窗口系统接收鼠标、键盘和其它事件,并且在屏幕上绘制自己。
QWidget 定义了针对XWindow 平台的虚函数:
清单12.
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 建立的连接句柄。
参考资料
?Qt官方网站
?Xorg 官方网站
?Qt兴趣邮件列表
?Qt的信号与槽机制介绍