Version 4 of Using Windows Controls in Tk Windows

Updated 2002-07-18 20:37:51

Developers occasionally ask questions about using non-Tk windows within Tk windows under the Microsoft Windows operating system enironment. Typically, developers 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 creatioin skeleton supplied with Tk distributions, 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 dialog, 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 widget. 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.

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:

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

                   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

                            break;
                            }

                return 0L;
                }

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 propper 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: