Updated 2006-05-09 19:23:03

Developers occasionally ask questions about using non-Tk windows within Tk windows under the Microsoft Windows operating system environment. Typically, developpers may wish to use their own custom controls, or some of the standard windows controls.

The issue under MS windows is that each GUI interface element is itself a window which has a "window procedure" that is responsible for drawing the window contents in response to messages dispatched by a message loop. Each application under MS Windows has at least 1 message loop that dispatches messages to all of the application windows. This scheme is not different from other GUI implementations, such as X Windows, Open GL and many GUI toolkits.

Tk windows, such as the "frame" window, are implemented as normal windows with a window procedure that maintains the appearance of the "frame" in response to messages dispatched by the Tk message loop. Tk implements geometry management through a system of widget path names that are used internally by Tk to identify windows and act on them in response to messages such as WM_MOVE, WM_SIZE, WM_DESTROY, and WM_PAINT. A private window has no widget path name, so the Tk geometry manager does not have a way of finding the private window and dispatching the appropriate messages to its window procedure. The result of this situation is that while one may easily create a private window that is the child of a Tk window, it will never receive any messages from the message queue, and therefore, will not be updated as required.

There are a number of solutions to this problem. The most straightforward one is to use the standard widget creation skeleton supplied with Tk distributions, the square.c example, and write your private window drawing functions using the X Windows emulation layer. This results in a private widget that is handled by the Tk geometry manager. Unfortunately, this is not an obviously useful solution for things like common controls and dialogs, nor for private window collections that may be derived from other applications.

The solution that I have employed is to capture the window procedure for a standard "frame" widget that just exactly wraps the private window I want to use. I then forward the messages that the "frame" widget gets from the event loop to the window procedure for my private window. At first blush, this appears to be a daunting task, as there are huge numbers of Windows messages that could be sent. Pratically speaking, however, one can either simply forward all messages received, or, more typically, just forward the WM_PAINT, WM_ERASEBKGRND, WM_MOVE, WM_SIZE and WM_FOCUS messages.

The process of capturing the window procedure is known as "sub-classing" the window. The window structure, accessed through its handle, has the address of the "frame" widget's window procedure. There are API functions, such as "GetWindowLong", or "GetClassLong", that, with appropriate parameters, will grab the address of the window procedure. You save this value, then install the address of your own window procedure using the "SetWindowLong" or "SetClassLong" API calls. The "tk.h" header file has macros that can be used to get the Windows handle for a Tk widget. Once you know the widget path name, you can, therefore, get its handle and modify its window procedure address.

In effect, your new window procedure is an alias for the "frame" widget's window procedure. When the Tk geometry manager dispatches a message to the "frame", your procedure will get the message. Since, presumably, your application also created the private control you are trying to use, you know its window handle, so you can use the "SendMessage" API call to forward the (relevant) messages to your window.

Typically, what you want to happen is you wish your private control to constantly fill the "frame" widget's client area, and to respond to displacements of the "frame" widget, and to update itself whenever the "frame" widget gets a WM_PAINT or WM_ERASEBKGRND message. You also want to destroy your private control before the "frame" gets destroyed, and to clean up your alias code before that happens. If you don't handle the WM_DESTROY message, your screen will be left with orphan windows that won't go away until you press Ctrl-Alt-Del.

Your alias window procedure, in the traditional Windows programming paradigm, might look like this:
 static HWND hWndMyControl;           /* The handle of my private control */
 static FARPROC *pTheFrameWndProc;    /* The address of the Tk frame,s WndProc */

     LRESULT WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARM lParam) {

     LRESULT result = 0L;

                   switch(msg) {

 case WM_MOVE:                      /* The frame has moved */
                            MoveWindow(hWndMyControl,......);
                            break;

 case WM_SIZE:                      / The frame changed size */
                            MoveWindow(hWndMyControl,....);
                            break;

 case WM_DESTROY:                    /* the frame is destroyed */
                            DestroyWindow(hWndMyControl);
                            CleanUpMyStuff();
                            break;
 .....

 default:                    ...call the private window's window proc...
                            result = (*pTheFrameWndProc)(hwnd,msg,lParam,lParam);
                            break;
                            }

                return result;
                }

The above is very skeletal. Depending on what you are doing, you may handle other messages, and you may want to call the "frame" widget's original window procedure before returning, and you may want to return different values according to the results of your private window procedure.

C++ programmers will usually be able to find appropriate wrapper classes that greatly simplify the above approach to using private controls. Clearly, reading the Win32 API documentation is a good idea before trying this at home. Be aware that failure to perform proper cleanup will not only leave orphan windows lying about, it will inevitably lead to what are commonly called "memory leaks", which, under Windows, can easily exhaust things like GDI resources, manifesting rather bizarre behaviour in completely unrelated applications.

See also:


Category Windows