初级X编程

本文详细内容请参考:

http://blog.csdn.net/coldwindflyrain/archive/2009/03/18/4001257.aspx

绪论

本教程是有关X窗口编程的"would-be"系列教程的第一部。单方面来说,这个教程是没用的,因为一个真正的X窗口程序员通常会使用抽象级更高 的库,例如Modif(或者是它的自由版本lesstiff),GTK,QT或者其它类似的库。但...也许我们应该从某个更易于学习理解的地方开始。因 为,知道它们到底是如何工作的应该永远不是个坏主意吧。

读过这个教程后,读者应该能够编写非常简单的X窗口图形程序,但不会有具体的应用是用这样的方法来写的,对于那些情况,应该用上面提到的那些抽象级 更高的库。


X窗口系统的客户/服务器模式

当初开发X窗口系统的主要目的只有一个,那就是灵活性。这个灵活性的意思就是说一件东西虽然看起来是在这工作,但却实际上是工作在很远的地方。因 此,较低等级的实现部分就必须提供绘制窗口,处理用户输入,画画,使用颜色等工作的工具。在这个要求下,决定了系统被分成了两部分,客户端和服务器端。客 户端决定做什么,服务器端执行真正的绘图和接受用户的输入并把它发给客户端。

这种模式与我们一般习惯的客户端和服务器端的概念是正好相反的。在我们的情况下,用户就坐在服务器端控制的机器前,而客户端这时却是运行在远程主机 上。服务器端控制着显示屏,鼠标和键盘。一个客户端也许正连接着服务器端,要求给它画一个窗口(或者是一堆),并要求服务器端把用户对它的窗口的输入传给 它。结果,好几个客户端可能连接到了一个服务器端上-有的在运行一个电子邮件软件,有的在运行一个网页浏览器等。当用户输入了指令给窗口,服务器端就会把 指令打包成事件传给控制那个窗口的客户端,客户端根据接受到的事件决定干什么然后发送请求让服务器端去画什么。

以上介绍的会话都是通过X消息协议传输的。该协议是实现在TCP/IP协议上的,它允许在一个网络里的客户端访问这个网络里的任何服务器端。最 后,X服务器端可以和客户端运行在同一台机器上以获得性能优化(注意,一个X协议事件可能会达到上百KB),例如使用共享内存,或者使用Unix域 socket(在一个Unix系统的两个进程间创建一个本地通道进行通信的方法)。


图形用户接口(GUI)编程-异步编程模式

不像我们通常的令人愉快的程序,一个GUI程序通常使用异步编程模式,也就是下面要介绍的"事件驱动编程"。这个"事件驱动编程"的意思是说程序通 常都处于空闲状态,等待从X服务器发来的事件,等收到了事件,才根据事件做相应的事情。一个事件可能是"用户在屏幕某处x,y点击了鼠标左键",或者是" 你控制的窗口需要被重画"。因为程序要回应用户的请求,同时还需要刷新自己的请求队列,因此需要程序尽可能使用较短的事件来处理一个事件(例如,作为一条 公认的准则,不能超过200毫秒)。

这也暗示着当然存在需要程序处理很长时间才能完成的事件(例如一个到远程服务器的网络连接,或者是连接一个数据库,或者是不幸的要处理一个超大文件 的复制工作)。这都要求程序使用异步方式来处理而不是通常的同步方式。这时候就应该采用各种各样的异步编程方法来进行这些耗时的工作了,或者干脆把它们交 给一个线程或进程来进行。

根据以上的说明,一个GUI程序就应该像以下的方式来工作:

  1. 进行初始化工作
  2. 连接X服务器
  3. 进行与X相关的初始化工作
  4. 进行循环
    1. 从X服务器那里接受下一个事件
    2. 根据收到的事件发送各种绘图指令给X服务器
    3. 如果事件是个退出事件,结束循环
  5. 关闭与X服务器的连接
  6. 进行资源释放工作

Xlib的基本思想

X协议是非常复杂的,为了大家不用再辛辛苦苦把时间浪费在实现它上面,就有了一个叫"Xlib"的库。这个库提供了访问任何X服务器的非常底层的手 段。因为X协议已经被标准化了,理论上客户程序使用任何Xlib的实现都可以访问任何X服务器。在今天,这看起来可能很琐碎,但如果回到那个使用字符终端 和专有绘图方法的时代,这应该是一个很大的突破吧。实际上,你很快发现围绕瘦客户机,窗口终端服务器等领域会有许多多么令人兴奋的事情。


X显示

使用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()的用户文档。我们在这里为了避免过于复杂只使用了一小部分非常简单 的属性。


。。。 。。。

猜你喜欢

转载自socol.iteye.com/blog/896676