CS-360 Fall, 2000 Class 8 R. Eckert Child and Popup Windows To date we've used a main window program and predefined window controls (buttons, static text, etc.). This is OK if the controls have exactly the features needed. But sometimes we need to create custom windows. The most common type of custom window is called a child window, which is always attached to its parent. It is only visible if its parent is visible. It is always on top of its parent. If the parent is destroyed, the child window also is destroyed. Child windows are used to deal with a specific task, e.g., getting input from the user. They can also be used as a programming tool to break up a large screen area into smaller portions, each with its own message- processing function. Popup window--a type of child window that is not physically attached to its parent; it can be positioned anywhere on screen. (It does vanish when its parent is destroyed.) Popup windows are handy if the user needs to move things around on the screen. Creating a child window-- 1. Register a new window class for the child (RegisterClass()). 2. Create the child window using CreateWindow() with WS_CHILD style. 3. Write a separate message-processing function for the child window. Sending messages to a child window-- We can use SendMessage() to send messages to a child window (just as we did with Windows controls). We need to specify the child window's handle (obtained when the child window was created) and the message ID with its parameters. Frequently the message ID that is used is WM_USER, which is defined in Windows.h as a number which is greater than any of the predefined Windows messages. So you can use WM_USER to mean anything you like. USER messages are used to communicate between windows with separate message-processing functions. We can use WM_USER and above to define messages for any type of activity. For example, we could have a header file containing the following: #define WM_MYKILLCHILD WM_USER /* tell child window to vanish */ #define WM_MYMAXCHILD WM_USER+1 /* tell child window to maximize */ #define WM_MYMINCHILD WM_USER+2 /* tell child window to minimize */ Then these constants could be used in the switch (wMessage) statement in the child window's message-processing function. In addition, a child window can send messages to its parent and to other child windows. THE CHILD EXAMPLE PROGRAM-- Clicking on the main window's "Create" menu item produces a child window with a "Destroy Me" button and some text. Clicking the "Send Message" main menu item causes the caption of the child window to change. Clicking the "Destroy Me" button in the child window causes the child window to disappear. Registering the child window class-- The message processing function is specified as ChildProc() -- It will receive messages from any windows based on this class. Class Icon -- LoadIcon() will be used to load the standard IDI_APPLICATION icon, which means that when this child window is minimized, that icon will appear on the bar inside the parent window. Cursor shape -- The standard IDC_CROSS cursor is loaded with LoadCursor(). Background -- The LTGRAY_BRUSH background brush is used. Menu -- None will be used in child windows based on this class. Creating the child window-- Whenever the user clicks on the "Create" main menu item, the resulting WM_COMMAND message [LOWORD(wParam)=IDM_CREATE] is processed as follows: if(!hChild) hChild = CreateWindow ("ChildClass", "Child Window", WS_CHILD | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CAPTION | WS_SYSMENU, 10, 30, 200, 150, hWnd, NULL, hInstance, NULL) ; In other words, if the child window handle, hChild, is NULL (i.e., the child window doesn't already exist), the child window is created. This logic effectively prohibits us from having more than one of these child windows around simultaneously. Window styles--The window is a child window (WS_CHILD class style), is sizeable (WS_THICKFRAME class style), is iconifiable (WS_MINIMIZEBOX), is maximizeable (WS_MAXIMIZEBOX), has a caption and is thus moveable (WS_CAPTION), and has a system menu button (WS_SYSMENU). Position and size--Client area coordinates (pixels) relative to the upper lefthand corner of the parent window are used. hWnd--The parent window's handle; Windows stores this linkage between child and parent==>if the parent is destroyed, Windows will destroy the child. The child window's handle, hChild, returned by the call to CreateWindow(), is stored in a static variable. This handle is used later to send the child window a message. Sending a message to the child window-- When the user clicks the "Send Message" main menu item: a WM_COMMAND message with ID IDM_SEND is sent to the program's main window procedure (WndProc()). Its response is: if (hChild) SendMessage (hChild, WM_USER, 0,0); So that if the child window exists, SendMessage() is used to send a WM_USER message to the child window. When we registered the child window's class ("ChildClass"), ChildProc() was identified as its window procedure. So the message ends up being transferred to ChildProc(). The response to this WM_USER message is to call SetWindowText() in order to write the string "Got message from parent" on the child window's caption bar. The program's WndProc() also responds to WM_PAINT messages by displaying a string. It also responds to a WM_USER+1 message from the child window (child window is being destroyed) by setting hChild to NULL. This indicates that the child window is dead, so the user can again use the "Create" menu item to create another child window. The Child Window Procedure-- ChildProc()--Since this is a window procedure, it uses the same 4 parameters as the main window's WndProc(). ChildProc() receives all messages from the child window. In addition to handling the WM_USER message, this ChildProc() explicitly handles WM_CREATE, WM_COMMAND, and WM_PAINT messages. All other messages are sent to the DefWindowProc(), which takes care of all the basic window functionality such as allowing us to move the child window, etc. The WM_CREATE message is sent when the child window is first created. The response is to create a button control (style = WS_CHILD | BS_PUSHBUTTON | WS_VISIBLE, with the text "Destroy Me". So we have a situation here of 3- deep nesting of windows: Parent (main window) Child window Button Control When the button is clicked, a WM_COMMAND is sent to the button's parent (the child window that contains the button). The child window doesn't have a menu or any other controls, so the button is the only source of WM_COMMAND messages to the child window's ChildProc(). Its response to this message is to call DestroyWindow(hChild), which kills the child window. Prior to doing that, however, GetParent() is used to retrieve the handle to the child window's parent so that it can then send a WM_USER+1 message to the parent window. Note that in the main window procedure, this message is handled by setting the child window handle to NULL. This means that the user can once again use the "Create" menu item on the main window's menu bar to create another child window. Th WM_USER message is sent by the parent window's WndProc(). As indicated above, ChildProc() responds by changing the text in its caption bar. The processing of WM_PAINT results in getting a DC with BeginPaint() and outputting a line of text with TextOut(). Note that this allows us to cover the child window and expose it, without the loss of the line of text. Notice that both parent and child process WM_PAINT messages. This means that text is redrawn after expose events for either window. Notice also that the message processing function for the parent and the child are completely independent. Messages for the parent window are processed by WndProc(), and messages for the child by ChildProc(). This segmentation of the logic for each window into a separate function helps make complex Windows programs manageable. Each window ends up being its own little world. Also, this provides a certain "independence" among windows-- often you can design utility windows and their message processing functions that can be used in many different applications without change. Popup Windows-- Popup windows are similar to child windows, but they are not restricted to the parent window's client area. They can appear anywhere on screen, can cover parts of parent window and other program windows. Like child windows, however, popup windows can never be covered by their parent window (i.e., the parent window is always under its popups). If the parent is minimized, the popup automatically disappears, and it reappears when parent is restored. Popup windows can be handy for small utility programs, such as a window that shows the current cursor position in a painting program. They are also ideal for applications that have multiple independent sections, such as a communications program which supports a number of simultaneous terminal sessions in different popup windows. Popup windows are created with CreateWindow(). The WS_POPUP style (which is mutually exclusive with WS_CHILD) is used. Coordinates are interpreted as screen coordinates, not as client-area coordinates of the parent window (as for WS_CHILD style windows). The Big Picture-- Child and popup window behavior with respect to the parent window mimics a program window's behavior with respect to the Windows desktop (background screen). This is on purpose. The windows desktop is just another window (lacking a title bar and border). All running main program windows behave like children of the desktop window. When a main program window is minimized, it shrinks to an icon on the desktop just like the child window in the CHILD example shrinks to an icon in the corner of its parent window's client area. When writing a program with several movable child windows, we're creating a tiny world in our program's client area; i.e., our program's client area behaves just like the full Windows desktop. Using the same code for both main program windows and for child windows keeps the Windows system small and gives the environment a consistent behavior.