当窗口被显示在X服务器上时,它们通常按照一定组织体系来排序 - 每个窗口可以有子窗口,每个子窗口又可以有自己的子窗口。让我们来查看这个组织体系的一些特性,看看它们是如何来影响例如绘画和事件等处理。
根窗口,父窗口和子窗口
每一个屏幕上都有一个根窗口。根窗口总是占据整个屏幕尺寸。这个窗口无法被销毁,改变尺寸或者图标化。当一个应用程序创建了一些窗口,它先创建至少一个顶层窗口。在被映射到屏幕上后,这个窗口成为一个根窗口的直接子窗口。这个窗口在被映射到屏幕上之前,窗口管理器被告知什么发生了,然后,窗口管理器获得特权成为新顶层窗口的"父亲"。这通常被用来增加一个会包含新窗口的窗口和绘制框架,标题栏,系统菜单等。
一旦一个顶层窗口(当然它实际上不是一个顶层窗口,因为窗口管理器已经成为它的父窗口了)被创建了,应用程序可以在它里面创建它的子窗口。一个子窗口只能在它的父窗口里显示 - 如果试图把它移动到外面,出去的部分将被父窗口的边框给切掉。任何窗口都可以包含一个以上的子窗口,在这种情况下,这些子窗口将被放置在应用的内部栈上。当一个顶层窗口被打开 - 它的所有子窗口也将随着它被打开。
以下例子演示如何在一个给定的叫"win"的窗口里打开一个子窗口。
/* this variable will store the handle of the newly created child window. */
Window child_win;
/* these variables will store the window''s width and height. */
int child_win_width = 50;
int child_win_height = 30;
/* these variables will store the window''s location. */
/* position of the child window is top-left corner of the */
/* parent window. - 0,0. */
int child_win_x = 0;
int child_win_y = 0;
/* create the window, as specified earlier. */
child_win = XCreateSimpleWindow(display, win, child_win_x, child_win_y, child_win_width, child_win_height, child_win_border_width, BlackPixel(display, screen_num),
WhitePixel(display, screen_num));
事件传递
先前我们已经讨论了事件传递 - 如果一个窗口收到了一个它不处理的事件 - 它就把该事件发到它的父窗口去。如果那个父窗口也不处理该事件 - 那个父窗口就把该事件发到它的父窗口上去,接下来依此类推。这种行为对一个简单的Xlib程序是没什么用的,但对于抽象级更高的绘图库是有用的。这些抽象级更高的绘图库通常把某个特定窗口的事件联系到一个函数上去。在这种情况下,发送事件到特定的窗口并用适当的函数来处理就非常有用。
与窗口管理器进行交互
在我们察看了如何创建和绘制窗口之后,我们回过头来看一下我们的窗口是如何与它们的环境 - 整个屏幕和其它窗口进行交互的。首先,我们的程序需要与窗口管理器进行交互。窗口管理器有责任装饰被绘制的窗口(例如增加框架,一个图标化的按钮,一个系统菜单,一个标题栏),同时在窗口被图标化时绘制图标。它还管理屏幕里的窗口排列顺序以及其它可管理的任务。我们需要给它各种提示以让它以我们需要的方式来对待我们的窗口。
窗口属性
许多与窗口管理器交流的参数都通过叫"properties"的数据来传递。X服务器把这些属性贴到各种窗口上,同时把它们存储成一种可以被各种架构的系统所能读取的格式(记住,一个X客户程序可能运行在一台远程主机上)。属性可以是各种类型 - 数字,字符串,等等。大部分的窗口管理器提示函数使用文本属性。一个叫XStringListToTextProperty()的函数可以把C语言的字符串转换成X文本属性,转换后的结果就可以传给各色Xlib函数。以下是一个例子:
/* This variable will store the newly created property. */
XTextProperty window_title_property;
/* This is the string to be translated into a property. */
char* window_title = "hello, world";
/* translate the given string into an X property. */
int rc = XStringListToTextProperty(&window_title, 1, &window_title_property);
/* check the success of the translation. */
if (rc == 0) { fprintf(stderr, "XStringListToTextProperty - out of memory/n");
exit(1);}
函数XStringListToTextProperty()接收一个C字符串矩阵(在我们的例子里只有一个)和一个指向XTextProperty型变量的指针为参数,合并C字符串里的属性把值传到XTextProperty型变量里。成功时它返回一个非0值,失败时返回0(例如,没有足够的内存来完成操作)。
设置窗口名字和图标名字
我们需要做的第一件事就是给我们的窗口设置名字。使用函数XSetWMName()。窗口管理器也许会把这个名字显示在窗口标题栏或是在任务栏上。该函数接受3个参数:一个指向显示的指针,一个窗口句柄,和一个包含有我们设置的名字的XTextProperty变量。下面是我们如何做的:
/* assume that window_title_property is our XTextProperty, and is */
/* defined to contain the desired window title. */
XSetWMName(display, win, &window_title_property);
为了设置我们的窗口的图标化名字,我们将用相同的方式使用函数XSetWMIconName()。
/* this time we assume that icon_name_property is an initialized */
/* XTextProperty variable containing the desired icon name. */
XSetWMIconName(display, win, &icon_name_property);
设置满意的窗口尺寸
在各种情况下,我们希望让窗口管理器知道我们指定的窗口尺寸以及只允许用户在我们的限定下改变窗口尺寸。例如,一个终端窗口(像xterm),我们总是要求我们的窗口可以包含全部的行和列,因此我们就不能从中间截断我们的显示。在其它情况下,我们不希望我们的窗口可以被改变尺寸(像绝大部分的对话框窗口),等等。我们可以依赖窗口管理器的这个尺寸信息,虽然它可能被简单的忽视掉。我们首先需要创建一个数据结构来包含该信息,填充必要的数据,然后使用函数 XSetWMNormalHints()。下面是如何操作:
/* pointer to the size hints structure. */
XSizeHints* win_size_hints = XAllocSizeHints();
if (!win_size_hints) { fprintf(stderr, "XAllocSizeHints - out of memory/n");
exit(1);}
/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the minimal size as well as the initial size. */
win_size_hints->flags = PSize | PMinSize;
/* next, specify the desired limits. */
/* in our case - make the window''s size at least 300x200 pixels.*/
/* and make its initial size 400x250. */
win_size_hints->min_width = 300;win_size_hints->min_height = 200;
win_size_hints->base_width = 400;win_size_hints->base_height = 250;
/* pass the size hints to the window manager. */
XSetWMNormalHints(display, win, win_size_hints);
/* finally, we can free the size hints structure. */
XFree(win_size_hints);
请查看你的手册来获取尺寸提示的完整信息。
设置各种窗口管理器提示
使用函数XSetWMHints()还可以设置许多其它的窗口管理器提示。该函数使用一个XWMHints结构来传递参数给窗口管理器。下面是例子:
/* pointer to the WM hints structure. */
XWMHints* win_hints = XAllocWMHints();
if (!win_hints) { fprintf(stderr, "XAllocWMHints - out of memory/n");
exit(1);}
/* initialize the structure appropriately. */
/* first, specify which hints we want to fill in. */
/* in our case - setting the state hint as well as the icon position hint. */
win_hints->flags = StateHint | IconPositionHint;
/* next, specify the desired hints data. */
/* in our case - make the window''s initial state be iconized, */
/* and set the icon position to the top-left part of the screen. */
win_hints->initial_state = IconicState;win_hints->icon_x = 0;
win_hints->icon_y = 0;
/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);
/* finally, we can free the WM hints structure. */
XFree(win_hints);
请查阅手册以获取全部选项的详细信息。
设置一个程序的图标
在用户图标化了我们的程序的时候,为了让窗口管理器能为我们的程序设置一个图标,我们使用上面提到的函数XSetWMHints。但是,首先我们需要创建一个包含有图标数据的像素图。X服务器使用像素图来操作图片,将在后面介绍它的详细使用。在这里,我们只是向你展示如何为你的程序设置图标。我们假设你已经得到了一个X bitmap格式的图标文件。教程为了方便提供了一个图标文件"icon.bmp" ,下面是代码:
/* include the definition of the bitmap in our program. */
#include "icon.bmp";
/* pointer to the WM hints structure. */
XWMHints* win_hints;
/* load the given bitmap data and create an X pixmap containing it. */
Pixmap icon_pixmap = XCreateBitmapFromData(display, win, icon_bitmap_bits, icon_bitmap_width, icon_bitmap_height);
if (!icon_pixmap) {
fprintf(stderr, "XCreateBitmapFromData - error creating pixmap/n");
exit(1);}
/* allocate a WM hints structure. */
win_hints = XAllocWMHints();
if (!win_hints) {
fprintf(stderr, "XAllocWMHints - out of memory/n");
exit(1);}
/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the icon''s pixmap. */
win_hints->flags = IconPixmapHint;
/* next, specify the desired hints data. */
/* in our case - supply the icon''s desired pixmap.*/
win_hints->icon_pixmap = icon_pixmap;
/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);
/* finally, we can free the WM hints structure. */
XFree(win_hints);
你可以使用程序例如"xpaint"来创建使用X bitmap格式的文件。
我们提供文件simple-wm-hints.c来总结这一节,这段程序包括创建一个窗口,设置窗口管理器提示为在上面显示,以及一个简单的事件循环。它允许用户调整参数以察看提示是如何影响程序的行为的。这可以帮助你了解X程序的可移植性。
简单窗口操作
对我们的窗口,我们可以做更多的一些事情。例如,改变它们的尺寸,打开或关闭它们,图标化它们等。Xlib提供了一系列函数来完成上面提到的功能。
映射和解除一个窗口的映射
首先我们对窗口作的一对操作是映射它到屏幕上去和解除它的映射。映射一个窗口的操作将会使一个窗口显示在屏幕上,如我们在简单窗口程序例子里所看到的。解除映射操作将会把窗口从屏幕里移除出去(虽然作为一个逻辑结点它仍然在X服务器里)。这个可以提供产生窗口被隐藏(映射解除)和再显示(映射)的效果。例如,我们的程序里有一个对话框,我们不需要每次在需要它显示的时候都重新创建一个窗口,我们只是以映射解除的状态创建一次,在用户需要的时候简单的把它映射到屏幕上去就行了。这比每一次都创建它和销毁它要快多了,当然,这需要在客户端和服务器端同时使用更多的内存。
你应该还记得映射操作是使用函数XMapWindow()。映射解除操作是使用函数XUnmapWindow(),下面是如何使用它们:
/* make the window actually appear on the screen. */
XMapWindow(display, win);
/* make the window hidden. */
XUnmapWindow(display, win);
除非整个窗口被其它窗口给覆盖了,一个暴露事件将在映射操作后发给应用程序。
在屏幕上移动一个窗口
我们想做的另一个操作是在屏幕里移动窗口。使用函数XMoveWindow()可以完成这个操作。它接受窗口的新坐标,使用的方法和函数XCreateSimpleWindow()是一样的。一下是调用的例子:
/* move the window to coordinates x=400 and y=100. */
XMoveWindow(display, win, 400, 100);
注意当窗口移动的时候,窗口的部分可能后被遮住或被重新暴露,这样我们就可能会收到暴露事件。
改变窗口尺寸
接下来我们要做的是改变一个窗口的尺寸。使用函数XResizeWindow()可以完成这个操作:
/* resize the window to width=200 and height=300 pixels. */
XResizeWindow(display, win, 200, 300);
我们可以合并移动和改变尺寸操作为一个操作,使用函数XMoveResizeWindow():
/* move the window to location x=20 y=30, and change its size */
/* to width=100 and height=150 pixels. */
XMoveResizeWindow(display, win, 20, 30, 100, 150);
改变窗口们的栈顺序 - 提升和降低
到目前为止我们已经改变了一个单独窗口的许多属性。接下来我们将看看窗口之间的属性。其中一个就是它们的栈属性。也就是说,窗口是如何在屏幕上排列的。最前面的窗口我们说它是在栈顶,最后面的窗口我们说它是在栈底。下面演示我们如何改变窗口的栈顺序:
/* move the given window to the top of the stack. */
XRaiseWindow(display, win1);
/* move the given window to the bottom of the stack. */
XLowerWindow(display, win1);
图标化和恢复一个窗口
在这里我们将要讲解的最后一个操作就是如何把一个窗口变换成图标状态和恢复它。使用函数XIconifyWindow()来把一个窗口变换成图标状态,使用函数XMapWindow()来恢复它。为了帮助理解为什么图标化函数没有一个对应的反函数,我们必须理解当一个窗口被图标化时,实际发生的事情是那个窗口被解除映射了,而它的图表被映射了。结果,如果想使哪个窗口在出现,我们只需要简单的映射它一下就行了。图标实际上是另一个窗口,只不过它与我们的窗口有非常强的联系关系。下面演示如何图标化一个窗口并恢复它:
/* iconify our window. Make its icon window appear on the same */
/* screen as our window (assuming we created our window on the */
/* default screen). */
XIconifyWindow(display, win, DefaultScreen(display));
/* de-iconify our window. the icon window will be automatically */
/* unmapped by this operation. */
XMapWindow(display, win);
获得一个窗口的信息
与可以为窗口设置许多属性相同,我们也可以要求X服务器提供这些属性的值。例如,我们可以检查窗口现在在屏幕里什么位置,当前尺寸,是否被映射了等等。函数XGetWindowAttributes()可以帮助我们获取那些信息:
/* this variable will contain the attributes of the window. */
XWindowAttributes win_attr;
/* query the window''s attributes. */
Status rc = XGetWindowAttributes(display, win, &win_attr);
结构体XWindowAttributes包含了很多数据域,下面是它的一部分:
int x, y;
窗口的位置,相对于它的父窗口。
int width, height;
窗口的宽和高(单位,像素)。
int border_width
窗口的边框宽度
Window root;
根窗口,也就是我们的窗口在那个窗口里被显示了。
这个函数有些问题,就是它返回的是相对于父窗口的位置。这对一些窗口的操作(例如XMoveWindow)是没有什么意义的。为了解决这个问题,我们需要使用两步的操作。首先,我们找出窗口的父窗口的ID。然后我们在使用它来确定窗口相对于屏幕的坐标。我们使用两个前面没有介绍的函数来完成这个计算,XQueryTree()和XTranslateCoordinates()。这两个函数的功能超出了我们的需要,所以我们只关注我们需要的:
/* these variables will eventually hold the translated coordinates. */
int screen_x, screen_y;
/* creen_y contain the location of our original */
/* window, using screen coordinates. */
你可以看到Xlib有时候会让我们处理问题时变得很麻烦。