Version 1 of Using Xlib With Tcl/Tk

Updated 2002-01-22 17:42:17

By George Peter Staplin

The original is here: http://www.xmission.com/~georgeps/Xlib_TclTk.html

This tutorial demonstrates how to use Xlib with Tcl/Tk. In the example provided, Xlib is used to draw two boxes in a Tk window. A working C program is provided at the end of this tutorial, that should compile in OpenBSD, Linux, and other Unix like systems. Most Tcl/Tk C programming examples provide information about writing a complete widget. This tutorial is designed in mind for using Tcl/Tk as a C library.

It is assumed that the reader knows the basics of C, Xlib, Tk, and compiling programs.


Getting Started

The first step is to create a main for your application. Place the main function after the app_init and other commands that you will create.

  int main(int argc, char *argv[]) {

  /*Tk_Main automatically calls Tcl_CreateInterp()*/
  Tk_Main(argc, argv, app_init);

  } 

The beginning of the app_init procedure looks like this:

  int app_init (Tcl_Interp *interp) {

        if (Tcl_Init (interp) != TCL_OK) {
        fprintf (stderr, "%s", Tcl_GetStringResult(interp));
        exit (0);
        }

        if (Tk_Init (interp) != TCL_OK) {
        fprintf (stderr, "%s", Tcl_GetStringResult(interp));
        exit (0);
        }

  /*Create new commands and enter an event loop*/
  }

If an error occurs when loading Tcl or Tk, an error message will be reported to the console and the program will exit. Below is a short snippet taken from tcl.h that explains the return definitions, such as TCL_OK, which is used above.


Tcl Return Definitions

  • TCL_OK

Command completed normally; the interpreter's result contains the command's result.

  • TCL_ERROR

The command couldn't be completed successfully; the interpreter's result describes what went wrong.

  • TCL_RETURN

The command requests that the current procedure return; the interpreter's result contains the procedure's return value.

  • TCL_BREAK

The command requests that the innermost loop be exited; the interpreter's result is meaningless.

  • TCL_CONTINUE

Go on to the next iteration of the current loop; the interpreter's result is meaningless.


Command Creation

The next step is to create a command in the app_init proc that can be executed by a Tcl script.

  Tcl_CreateCommand (interp, "draw_test_cmd", draw_test_cmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL);

Briefly, the first argument is the Tcl command name. The next is the command proc. ClientData would be any argument(s) that you wish to pass to the draw_test_cmd procedure. Tcl_CmdDeleteProc would be a procedure that does something before running the draw_test_cmd. Two are NULL, because they are not needed in this simple program.


The main window is the window created automatically by Tk.

  Tk_Window image_window;

  image_window = Tk_MainWindow (interp);

Some of the Xlib functions need to know what the display connection is. Tk automatically invokes XOpenDisplay, so all that you need to do is a assign the result of Tk_Display to a Display structure.

  Display *display;
  display = Tk_Display (image_window);

This creates the window, because Tk delays creating the window until an idle moment. Then the window is mapped, because Tk delays mapping until an idle moment. By mapping, I mean displaying the window on the screen.

  Tk_MakeWindowExist (image_window);
  Tk_MapWindow (image_window);

Some Xlib functions need to know the color depth.

  int depth;

  depth = Tk_Depth (image_window);

Color and Graphics Context Allocation

This code creates two graphics contexts; which are used to draw a rectangle later on. You could also use Tk_GetGC instead.

  Colormap colormap;
  char green[] = "sea green";
  XColor green_col;
  GC green_gc;
  char red [] = "#FF0000";
  XColor red_col;
  GC red_gc;

  colormap = DefaultColormap (display, 0);
  XParseColor (display, colormap, green, &green_col);
  XAllocColor (display, colormap, &green_col);
  green_gc = XCreateGC (display, Tk_WindowId (image_window), 0, 0);
  XSetForeground (display, green_gc, green_col.pixel);

  XParseColor (display, colormap, red, &red_col);
  XAllocColor (display, colormap, &red_col);
  red_gc = XCreateGC (display, Tk_WindowId (image_window), 0, 0);
  XSetForeground (display, red_gc, red_col.pixel);

The code below creates the interface.

  Tcl_Eval (interp, "\n"
    "button .b -text \"Go\" -command {draw_test_cmd};\n"
    "pack .b\n"
    "wm geometry . 400x400\n"
    "canvas .c -height 200 -width 200\n"
    "pack .c -fill both -expand yes\n"
    "#Update is needed so that the canvas is created\n"
    "#before invoking draw_test_cmd.  Without this\n"
    "#the program might crash with a bad drawable\n"
    "#error.\n"
    "update\n"
    "\n");

(Note: string literals may not fall off the end of the line, in ANSI C. They can, however, be concatenated. Corrected above example. -EE Mar/05/2001)


  Tk_Window canvas;

  canvas = Tk_NameToWindow (interp, ".c", image_window);

This invokes the Xlib function when the window is exposed, moved, resized, and raised.

  Tcl_Eval (interp, "bind .c <Expose> {draw_test_cmd}");
  Tcl_Eval (interp, "bind .c <Configure> {draw_test_cmd}");

This is the simple event loop, which is actually just code from Tk_MainLoop. I feel that it's important that you understand how it works, because often calling Tcl_DoOneEvent is needed when working with two event loops.

  while (Tk_GetNumMainWindows () > 0) {
  Tcl_DoOneEvent (0);
  }

The actual draw_test_cmd is at the beginning of the C file. The first line below is a function prototype.

  int draw_test_cmd (ClientData clientData, Tcl_Interp *interp, 
  int argc, char *argv[]);

Drawing Into the Window

Below is the entire draw_test_cmd.

  int draw_test_cmd (ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
  {
  int c;


  fprintf (stdout, "\ndraw_test_cmd has been invoked.\n");

  /* This could make one call to Tk_WindowId and store the result in 
  a X Window typedef, but I think that it makes it more understandable
  for new programmers to have it this way.
  */

  XClearWindow (display, Tk_WindowId (canvas));

  /* This updates the window, so that if the button was pressed, it 
  will go up when released, before drawing the boxes.  This also 
  causes Tk to redraw before I draw, so that Tk doesn't overwrite 
  what I have drawn.
  */

  Tcl_Eval (interp, "update");

  /*This is a little bit of code that draws a checker board type of thing.*/
        for (c = 0; c < 150; c++) {
        XFillRectangle (display, Tk_WindowId (canvas), green_gc, 1, 1, c, c);
        XFlush (display);
        usleep (1);
        }

        for (c = 0; c < 150; c++) {
        XFillRectangle (display, Tk_WindowId (canvas), red_gc, 150, 150, c, c);
        XFlush (display);
        usleep (1);
        }

  return TCL_OK;
  }

http://www.xmission.com/~georgeps/Xlib_TclTk.png

Download Xlib_TclTk.tgz Example [L1 ]

See also: Drawing Into Foreign Windows. Also, BLT has a "container" widget, about which George Howlett has written ??.