使用XLib的基本思想就是X显示。它代表了一个打开的到X服务器的连接的结构。它隐藏了一个保存有从X服务器来的事件的队列,和一个保存客户程序准备发往服务器的请求队列。在Xlib里,这个结构被命名为显示"Display"。
当我们打开了一个到X服务器的连接,库就会返回一个指向这个结构的指针。然后,我们就可以使用这个指针来使用Xlib里各种各样的函数。
GC - 图形上下文
当我们进行各种绘图操作(图形,文本等)的时候,我们也许会使用许多参数来指定如何绘制,前景,背景,使用什么颜色,使用什么字体等等,等等。为了避免为每个绘图函数设置数量惊人的参数,我们使用一个叫"GC"的图形上下文结构。我们在这个结构里设置各种绘图参数,然后传给绘图函数就行了。这应当是一个非常方便的方法吧,尤其当我们在进行一连串操作中使用相同的参数时。
对象句柄
当X服务器为我们创建了各种各样的对象的时候 - 例如窗口,绘图区和光标 - 相应的函数就会返回一个句柄。这是一个存在在X服务器空间中的对象的一个标识-而不是在我们的应用程序的空间里。在后面我们就可以使用Xlib的函数通过句柄来操纵这些对象。X服务器维护了一个实际对象到句柄的映射表。Xlib提供了各种类型来定义这些对象。虽然这些类型实际上只是简单的整数,但我们应该继续使用这些类型的名字 - 理由是为了可移植。
Xlib结构的内存分配
Xlib的接口使用了各种类型的结构。有些可以由用户直接来分配内存,有些则只能使用专门的Xlib库函数来分配。在使用库来分配的情况,库会生成有适当初始参数的结构。这对大家来说是非常方便的,指定初始值对于不太熟练的程序员来说是非常头疼的。记住-Xlib想要提供非常灵活的功能,这也就意味着它也会变得非常复杂。提供初始值设置的功能将会帮助那些刚开始使用X的程序员们,同时不会干扰那些高高手们。
在释放内存时,我们使用与申请的同样方法来释放(例如,使用free()来释放malloc()申请的内存)。所以,我们必须使用XFree()来释放内存。
事件
一个叫"XEvent"的结构来保存从X服务器那里接受到的事件。Xlib提供了非常大量的事件类型。XEvent包括事件的类型,以及与事件相关的数据(例如在屏幕什么地方生成的事件,鼠标键的事件等等),因此,要根据事件类型来读取相应的事件里的数据。这时,XEvent结构使用c语言里的联合来保存可能的数据(如果你搞不清楚c的联合是怎么回事,那你就得花点时间再读读你的教科书了)。结果,我们就可能受到XExpose事件,一个XButton事件,一个XMotion事件等等。
编译基于Xlib的程序
编译基于Xlib的程序需要与Xlib库连接。可以使用下面的命令行:
cc prog.c -o prog -lX11
如果编译器报告找不到X11库,可以试着加上"-L"标志,像这样:
cc prog.c -o prog -L/usr/X11/lib -lX11
或者这样(针对使用X11的版本6)
cc prog.c -o prog -L/usr/X11R6/lib -lX11
在SunOs 4 系统上,X的库被放到了 /usr/openwin/lib
cc prog.c -o prog -L/usr/openwin/lib -lX11
等等,具体情况具体分析
打开,关闭到一个X服务器的连接
一个X程序首先要打开到X服务器的连接。我们需要指定运行X服务器的主机的地址,以及显示器编号。X窗口允许一台机器开多个显示。然而,通常只有一个编号为"0"的显示。如果我们想要连接本地的显示(例如进行显示的机器同时又是客户程序运行的机器),我们可以直接使用":0"来连接。现在我们举例,连接一台地址是"simey"的机器的显示,我们可以使用地址"simey:0",下面演示如何进行连接
#include <X11/Xlib.h> /* defines common Xlib functions and structs. */
/* this variable will contain the pointer to the Display structure */
/* returned when opening a connection. */
Display* display;
/* open the connection to the display "simey:0". */
display = XOpenDisplay("simey:0");
if (display == NULL) {
fprintf(stderr, "Cannot connect to X server %s/n", "simey:0");
exit (-1);
}
注意,通常要为X程序检查是否定义了环境变量"DISPLAY",如果定义了,可以直接使用它来作为XOpenDisplay()函数的连接参数。
当程序完成了它的工作且需要关闭到X服务器的连接,它可以这样做:
XCloseDisplay(display);
这会使X服务器关闭所有为我们创建的窗口以及任何在X服务器上申请的资源被释放。当然,这并不意味着我们的客户程序的结束。
检查一个显示的相关基本信息
一旦我们打开了一个到X服务器的连接,我们应该检查与它相关的一些基本信息:它有什么样的屏幕,屏幕的尺寸(长和宽),它支持多少颜色(黑色和白色?灰度级?256色?或更多),等等。我们将演示一些有关的操作。我们假设变量"display"指向一个通过调用XOpenDisplay()获得的显示结构。
/* this variable will be used to store the "default" screen of the */
/* X server. usually an X server has only one screen, so we're only */
/* interested in that screen. */
int screen_num;
/* these variables will store the size of the screen, in pixels. */
int screen_width;
int screen_height;
/* this variable will be used to store the ID of the root window of our */
/* screen. Each screen always has a root window that covers the whole */
/* screen, and always exists. */
Window root_window;
/* these variables will be used to store the IDs of the black and white */
/* colors of the given screen. More on this will be explained later. */
unsigned long white_pixel;
unsigned long black_pixel;
/* check the number of the default screen for our X server. */
screen_num = DefaultScreen(display);
/* find the width of the default screen of our X server, in pixels. */
screen_width = DisplayWidth(display, screen_num);
/* find the height of the default screen of our X server, in pixels. */
screen_height = DisplayHeight(display, screen_num);
/* find the ID of the root window of the screen. */
root_window = RootWindow(display, screen_num);
/* find the value of a white pixel on this screen. */
white_pixel = WhitePixel(display, screen_num);
/* find the value of a black pixel on this screen. */
black_pixel = BlackPixel(display, screen_num);
还有很多其它的宏来帮助我们获取显示的信息,你可以在Xlib里的参考里找到。另外还有很多相当的函数可以完成相同的工作。
创建一个基本的窗口 - 我们的"Hello world"程序
在我们获得一些窗口的基本信息之后,我们就可以开始创建我们的第一个窗口了。Xlib支持好几个函数来创建窗口,它们其中的一个是XCreateSimpleWindow()。这个函数使用很少的几个参数来指定窗口的尺寸,位置等。以下是它完整的参数列表:
Display* display
指向显示结构的指针
Window parent
新窗口的父窗口的ID。
int x
窗口的左上X坐标(单位为屏幕像素)
int y
窗口的左上Y坐标(单位为屏幕像素)
unsigned int width
窗口的宽度(单位为屏幕像素)
unsigned int height
窗口的高度(单位为屏幕像素)
unsigned int border_width
窗口的边框宽度(单位为屏幕像素)
unsigned long border
用来绘制窗口边框的颜色
unsigned long background
用来绘制窗口背景的颜色
让我们创建一个简单的窗口,它的宽度是屏幕宽的1/3,高度是屏幕高的1/3,背景色是白色,边框是黑色,边框的宽度是2个像素。该窗口将会被放置到屏幕的左上角。
/* this variable will store the ID of the newly created window. */
Window win;
/* these variables will store the window's width and height. */
int win_width;
int win_height;
/* these variables will store the window's location. */
int win_x;
int win_y;
/* calculate the window's width and height. */
win_width = DisplayWidth(display, screen_num) / 3;
win_height = DisplayHeight(display, screen_num) / 3;
/* position of the window is top-left corner - 0,0. */
win_x = win_y = 0;
/* create the window, as specified earlier. */
win = XCreateSimpleWindow(display,
RootWindow(display, screen_num),
win_x, win_y,
win_width, win_height,
win_border_width, BlackPixel(display, screen_num),
WhitePixel(display, screen_num));
事实上我们创建窗口并不意味着它将会被立刻显示在屏幕上,在缺省情况下,新建的窗口将不会被映射到屏幕上,它们是不可见的。为了能让我们创建的窗口能被显示到屏幕上,我们使用函数XMapWindow():
XMapWindow(win);
如果想察看目前为止我们所举的例子的代码,请参看源程序simple-window.c。你将会发现两个新的函数 -
XFlush() 和XSync()。函数XFlush()刷新所有处于等待状态的请求到X服务器 - 非常像函数fflush()刷新所有的内容到标准输出。XSync()也刷新所有处于等待状态的请求,接着等待X服务器处理完所有的请求再继续。在一个一般的程序里这不是必须的(据此你可以发现我们什么时候只是写一个一般的程序),但我们现在把它们提出来,尝试在有或没有这些函数的情况下程序不同的行为。
在窗口里绘制
在窗口里绘图可以使用各种绘图函数 - 画点,线,圈,矩形,等等。为了能在一个窗口里绘图,我们首先需要定义各种参数 - 如线的宽度,使用什么颜色,等等。这都需要使用一个图形上下文(GC)。
申请一个图形上下文(GC)
如我们已经提到的,一个图形上下文定义一些参数来使用绘图函数。因此,为了绘制不同的风格,我们可以在一个窗口里使用多个图形上下文。使用函数 XCreateGC()可以申请到一个新的图形上下文,如以下例(在这段代码里,我们假设"display"指向一个显示结构,"win"是当前创建的一个窗口的ID):
/* this variable will contain the handle to the returned graphics context. */
GC gc;
/* these variables are used to specify various attributes for the GC. */
/* initial values for the GC. */
XGCValues values = CapButt | JoinBevel;
/* which values in 'values' to check when creating the GC. */
unsigned long valuemask = GCCapStyle | GCJoinStyle;
/* create a new graphical context. */
gc = XCreateGC(display, win, valuemask, &values);
if (gc < 0) {
fprintf(stderr, "XCreateGC: /n");
}
注意,应该考虑一下变量"valuemask"和"values"的角色。因为一个图形上下文有数量惊人的属性,而且通常我们只需要设置里面的一部分,所以我们需要告诉XCreateGC()什么属性是我们需要设置的,这也就是变量"valuemask"的作用。我们接着就可以通过"values"来指定真正的值。在这个例子里,我们定义了图形上下文里的两个属性:
1 当绘制一个多重部分的线时,线在连接时使用"Bevelian"风格
2 一条线的终端被画直而不是圆形
其它未指定的属性GC将会使用缺省值。
一旦我们创建了一个图形上下文,我们就可以在各种绘图函数里用它,我们也可以为了适应别的函数来变更它的属性。
/* change the foreground color of this GC to white. */
XSetForeground(display, gc, WhitePixel(display, screen_num));
/* change the background color of this GC to black. */
XSetBackground(display, gc, BlackPixel(display, screen_num));
/* change the fill style of this GC to 'solid'. */
XSetFillStyle(display, gc, FillSolid);
/* change the line drawing attributes of this GC to the given values. */
/* the parameters are: Display structure, GC, line width (in pixels), */
/* line drawing style, cap (line's end) drawing style, and lines */
/* join style. */
XSetLineAttributes(display, gc, 2, LineSolid, CapRound, JoinRound);
如果你想了解全部的图形上下文的属性设定方法,请参考函数XCreateGC()的用户文档。我们在这里为了避免过于复杂只使用了一小部分非常简单的属性。
绘图的基本元素 - 点,线,矩形,圆...
在我们创建了一个GC后,我们就可以通过GC在一个窗口里使用一系列的Xlib函数,这个函数的集合被称为"绘图的基本元素"。为了简便,让我们通过例子来看一看它们是怎么工作的。这里我们假设"gc"是一个前面创建好的GC,"win"是一个已经创建好的窗口的句柄。
/* draw a pixel at position '5,60' (line 5, column 60) of the given window. */
XDrawPoint(display, win, gc, 5, 5);
/* draw a line between point '20,20' and point '40,100' of the window. */
XDrawLine(display, win, gc, 20, 20, 40, 100);
/* draw an arc whose center is at position 'x,y', its width (if it was a */
/* full ellipse) is 'w', and height is 'h'. Start the arc at angle 'angle1' */
/* (angle 0 is the hour '3' on a clock, and positive numbers go */
/* counter-clockwise. the angles are in units of 1/64 of a degree (so 360*64 */
/* is 360 degrees). */
int x = 30, y = 40;
int h = 15, w = 45;
int angle1 = 0, angle2 = 2.109;
XDrawArc(display, win, gc, x-(w/2), y-(h/2), w, h, angle1, angle2);
/* now use the XDrawArc() function to draw a circle whose diameter */
/* is 15 pixels, and whose center is at location '50,100'. */
XDrawArc(display, win, gc, 50-(15/2), 100-(15/2), 15, 15, 0, 360*64);
/* the XDrawLines() function draws a set of consecutive lines, whose */
/* edges are given in an array of XPoint structures. */
/* The following block will draw a triangle. We use a block here, since */
/* the C language allows defining new variables only in the beginning of */
/* a block. */
{
/* this array contains the pixels to be used as the line's end-points. */
XPoint points[] = {
{0, 0},
{15, 15},
{0, 15},
{0, 0}
};
/* and this is the number of pixels in the array. The number of drawn */
/* lines will be 'npoints - 1'. */
int npoints = sizeof(points)/sizeof(XPoint);
/* draw a small triangle at the top-left corner of the window. */
/* the triangle is made of a set of consecutive lines, whose */
/* end-point pixels are specified in the 'points' array. */
XDrawLines(display, win, gc, points, npoints, CoordModeOrigin);
}
/* draw a rectangle whose top-left corner is at '120,150', its width is */
/* 50 pixels, and height is 60 pixels. */
XDrawRectangle(display, win, gc, 120, 150, 50, 60);
/* draw a filled rectangle of the same size as above, to the left of the */
/* previous rectangle. note that this rectangle is one pixel smaller than */
/* the previous line, since 'XFillRectangle()' assumes it is filling up */
/* an already drawn rectangle. This may be used to draw a rectangle using */
/* one color, and later to fill it using another color. */
XFillRectangle(display, win, gc, 60, 150, 50, 60);
如果你觉得已经抓住使用这些函数的要点,那我们后面的说明就会变得简单。我们将提到其它一些使用相同方法的函数。例如,XFillArc()使用与 XDrawArc()相同的参数,但它只画一个圆的内部(相似的,XFillRectangle()只画一个矩形区的内部)。另外还有一个函数 XFillPolygon()负责填充一个多边形的内部区域。它使用的参数差不多与XDrawLines()相同。但是要注意,如果提供在矩阵里的最后一个参数所代表的点与第一个点不在同一个位置上,函数XFillPolygon()会添加一条虚拟的线来连接那两个点。这两个函数的另外一个不同是,XFillPolygon()使用一个附加的参数,形状,这个参数可以帮助X服务器来优化它的绘图工作。你可以在手册里找到详细的内容。以上的函数还存在它们的复数绘制版本,命名为XFillArcs()和XFillRectangles()。