By George Peter Staplin1111111
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
Command completed normally; the interpreter's result contains the command's result.
The command couldn't be completed successfully; the interpreter's result describes what went wrong.
The command requests that the current procedure return; the interpreter's result contains the procedure's return value.
The command requests that the innermost loop be exited; the interpreter's result is meaningless.
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); f
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; }
Download Xlib_TclTk.tgz Example [L1 ]
See also: Drawing Into Foreign Windows. Also, BLT has a "container" widget, about which George Howlett has written [L2 ].