在XWindows中绘制时为什么有关系?

时间:2019-06-05 05:59:20

标签: c linux x11 xlib

我从网络上获得了这个XWindows“你好,世界”。我的行为在更复杂的程序中无法理解,但是这里的简单程序也会显示它:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void) {
   Display *d;
   Window w;
   XEvent e;
   const char *msg = "Hello, World!";
   int s;
   int x;

   d = XOpenDisplay(NULL);
   if (d == NULL) {
      fprintf(stderr, "Cannot open display\n");
      exit(1);
   }

   s = DefaultScreen(d);
   w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 100, 100, 1,
                           BlackPixel(d, s), WhitePixel(d, s));
   XSelectInput(d, w, ExposureMask | KeyPressMask);
   XMapWindow(d, w);

   XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));

   //XFlush(d);

   while (1) {
      XNextEvent(d, &e);
      if (e.type == Expose) {
//         XDrawString(d, w, DefaultGC(d, s), 10, 50, msg, strlen(msg));
      }
      if (e.type == KeyPress) break;
   }

   XCloseDisplay(d);
   return 0;
}

因此,第一个XDrawString()调用实际上不会写入窗口,但是在暴露事件中取消对它的注释。我发现这种行为令人困惑。在第一个XDrawString()时,显示,屏幕和窗口都已设置。但这并没有画出来,而出现在事件处理程序中的那画了。我还尝试了XFlush()进入队列,这没什么区别。

此效果与我正在处理的(许多)更复杂的代码有关,该代码将字符放置在屏幕上。我将它们放置在支持屏幕的像素图中的所需位置,然后对真实屏幕进行相同的绘制以使其保持同步。暴露处理程序会将像素图复制到屏幕上,以对其进行更新,但是暴露事件要等到以后才会发生,此外,将整个像素图复制回屏幕比放置单个字符要昂贵。

我怀疑我需要跟随绘制到缓冲区的操作,将字符下的矩形标记为无效(这将是MS Windows方式),并让事件处理程序来处理它,但是我想了解什么在X11内继续进行以实现这一目标。

4 个答案:

答案 0 :(得分:5)

X服务器不需要自己提供后备存储,因此在需要绘制窗口时会发送Expose事件。这与其他环境中的功能相同。一开始您可以进行绘制,但是X服务器可能尚未正确设置窗口,稍后会在准备将内容绘制到屏幕上时向您报告。绘图是直接显示在屏幕上的,而不是后备缓冲区,因此,每次将窗口置于前面时,都必须进行绘制。

关于您自己请求重画,您可以使用XSendEvent自己发送一个Expose事件,然后X会将其中继到您的事件循环中。因此,它实际上与Windows完全相同。

答案 1 :(得分:2)

请记住您处于客户端/服务器环境中。意味着在您的代码请求与其实现之间存在延迟。从调用返回到XMapWindow并不意味着您的窗口仅准备好接受XServer必须接受的图形,因此您需要等待第一个Expose事件进行绘制,这意味着XServer完成了这项工作。这是强制性的,因为Expose是表示该窗口(或其一部分)在屏幕上可见的事实的事件。

永远记住:UI是事件驱动的。

答案 2 :(得分:1)

只需在Sami的良好答复中加点。

深思熟虑,当使用多个可以相互重叠的窗口(不计算很多其他事情)时,X11的工作方式是唯一正确的方法。这就是为什么许多框架(X11,Windows,gtk,qt ...)都使用这种机制(无效并公开|绘制|绘制)。

所有有关优化图形的事情。您不应盲目绘画,因为您的绘画可能会被丢弃;可能是您的窗口不可见,或者是用户在不重绘其内容的情况下移动了该窗口,或者其他任何原因。

相反,X11(或其他工具包/框架)非常了解何时必须显示数据,并且可以显示 。在这种情况下,X11告诉您重绘(通过暴露事件)。当X11要求重新粉刷时,您的绘图操作将不会被丢弃。

另一方面,应该准备一个好的X11程序,以便在需要时进行重新绘制:如果用户在周围拖动窗口或更改虚拟桌面,对应用程序进行图标化等操作,它将经常发生。

合并两个方面:程序必须在需要时绘制,并且在创建其第一个窗口后将要求确定。因此,仅在暴露事件中绘制是完美的。

可以通过支持存储来改进此基本机制:X11可以缓存图形数据,并在以后使用它们,而不必一次又一次地向程序询问相同的数据。有时后备存储速度更快,但有时只会浪费内存。

答案 3 :(得分:1)

好吧,我昨晚想到了这一点,并意识到一个叫做“ lines”的古老而经典的程序说明了我正在谈论的问题:

#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int randr(int upper)

{

   return rand() % (upper+1);

}   

int main(void) {

    Display *d;
    Window w;
    XEvent e;
    const char *msg = "Hello, World!";
    int s;
    XEvent se;

    d = XOpenDisplay(NULL);
    if (d == NULL) {
       fprintf(stderr, "Cannot open display\n");
       exit(1);
    }

    s = DefaultScreen(d);
    w = XCreateSimpleWindow(d, RootWindow(d, s), 10, 10, 400, 400, 1,
                            BlackPixel(d, s), WhitePixel(d, s));
    XSelectInput(d, w, ExposureMask | KeyPressMask);
    XMapWindow(d, w);

    se.type = Expose;
    se.xexpose.type = Expose;
    se.xexpose.serial = 0;
    se.xexpose.send_event = 1;
    se.xexpose.window = w;
    se.xexpose.x = 0;
    se.xexpose.y = 0;
    se.xexpose.width = 400;
    se.xexpose.height = 400;
    se.xexpose.count = 0;

    while (1) {
       XNextEvent(d, &e);
       if (e.type == Expose) {
          XSetForeground(d, DefaultGC(d, s), randr(255)<<16 | randr(255)<<8 | randr(255));
          XDrawLine(d, w, DefaultGC(d, s), randr(400), randr(400), randr(400), randr(400));
          XSendEvent(d, w, 0, ExposureMask, &se);
       }
       if (e.type == KeyPress) break;
    }

    XCloseDisplay(d);

    return 0;
}

Lines是一个古老的程序,可以追溯到1970年代后期的Cromemco Dazzler。它只是画随机线,而且速度很快。它不需要输入,它只是画图,也可以用作性能测试。

正如您在这里看到的,我通过在暴露事件本身中发布另一个事件来欺骗X11连续处理暴露事件。在(例如)MS窗口中,这不是必需的。您可以随时在窗口上绘制,并且不读取事件队列不是很好,但是程序仍然可以工作。

该程序可以在X11中运行,并且(在我的机器上)运行如此之快,以至于基本上可以立即填满窗口。

现在,如果我想使该演示尽快进行,则需要考虑该程序中的主要时间,据我所知,这是需要持续发布新的公开事件。