CS-360 Fall, 2000 Class 5 R. Eckert CHILD WINDOW CONTROLS Child window controls are child windows with some of the following characteristics: -An application uses them in conjunction with another window. -Normally they are used for simple I/O tasks. -Let the user choose commands, view status, view/edit text, etc. -Properties, appearance, and behavior are determined by predefined class definitions. -But their behavior can be customized. -Allow an application to easily set up child windows for common objects: (buttons, scroll bars, etc.). -Allow the user to display and select information in standard ways. -The Windows Environment does most of work in: -painting/updating a Control's screen area. -determining what the user is doing. -So Controls can do the "dirty work" for the main window. -Often used as input devices for the parent window. -They constitute the "working components" of dialog boxes. -The Windows environment contains each control's WinProc() ==> -Messages to child windows are processed in a predefined way. -The programmer doesn't have to worry about it. -Main window communicates with controls by sending/receiving messages. -There are six standard control types: -Static Text -Button -Edit Control -List Box -Combo Box -Scroll Bar -All are windows. -The Windows Environment automatically repaints Control upon exposure. -An Example: WordPad ("File"|"Open") -- has most types of controls -There are 17 other predefined "Common Controls" (See Petzold, Ch. 12) Child Window Controls are Created with CreateWindow(); The control type is specified as a "Predefined Class Name" in one of the parameters. Predefined Control Class names-- STATIC--Used primarily to display text, but can also display icon images and rectangles. The advantage over painting text on the window's client area is that the static control is automatically redrawn if covered and uncovered by another window. Thus, we don't have to worry about processing WM_PAINT messages to keep the client area up to date. Static controls often act as labels for other controls. BUTTON--Can be clicked by the user to indicate actions to be taken or choices made. There are lots of different styles (e.g., check, radio, group). Typically they notify the parent window when the user chooses the control. LISTBOX--Contains lists of items that can be selected. The entire list is shown. EDIT--A single or multiline control that allows the user to enter, view and edit text. Lots of word processing capability is built in. COMBO BOX--Consists of either a static text box or an edit box combined with a list box. The list box can be displayed at all times or pulled down by the user. If the combo box contains a static text box, the text box always displays the selection (if any) in the list box portion of the combo box. If it uses an edit box, the user can type in the desired selection. The list box highlights the first item (if any) that matches what the user has entered in the edit box. The user can then select the item highlighted in the list box to complete the choice. SCROLLBAR--Lets the user choose the direction and distance to scroll (move) information in a related window. There are two types: 1. A control attached to edge of a parent window. Allows the user to "scroll" the information in the parent window's client area. 2. A stand-alone child window control that allows the user to enter or change an integer value by moving the scroll bar "thumb". Creating Child Window Controls-- CreateWindow()--one of the most complicated functions in Windows since it can create such a wide range of objects. It returns a handle (HWND) to the window it created. It can be used to create any kind of window, including a child window control. Recall the parameters: LPSTR lpClassName: Name of window class that the window being created is based on. Can be a new class defined with RegisterClass() or one of the predefined control classes: "STATIC", "BUTTON", "EDIT", "LISTBOX", "COMBOBOX", "SCROLLBAR". LPSTR lpWindowName: Name of the window--a pointer to a character string. For windows with caption bars, this will be the caption. For BUTTON, EDIT, STATIC classes, it is the text in the center of the control. For COMBOBOX, LISTBOX, SCROLLBAR, it is ignored (use ""). DWORD dwStyle: Window style; a series of bit values that tell Windows the properties needed for the window being created. Several styles can be combined with the bitwise or operator (|). int X: The X position of the upper left corner of the window. For a main window, the position is relative to the upper-left corner of the screen (screen coordinates); for child windows and window controls, the position is relative to the upper left corner of parent window's client area. It is measured in pixels. CW_USEDEFAULT can be used to let Windows decide where to put the window. int Y: The Y position. This is used just like X. int nWidth: The Width of window in pixels. CS_USEDEFAULT==>Windows chooses. int nHeight: The height of window in pixels. This is used just like nWidth. HWND hWndParent: Handle to the parent window. For the application's main window this is NULL; for Child windows and window controls: you must specify the handle of the parent window. HMENU hMenu: For windows that have menus, this is the handle of the menu to be used. NULL means to use the menu defined in the class definition. In the case of controls, which don't have menus, hMenu is used to hold the integer ID value for the control. This is very important since the ID value will be passed with the WM_COMMAND message that is generated when the user interacts with the control. This ID allows the program to identify which control was activated. HANDLE hInstance: A handle to the instance of the program creating the window. GetWindowLong() can be used to get this value. LPSTR lpParam: Normally NULL, this can be used to pass data with the WM_CREATE message sent by Windows when window is created. Window Styles-- There are a whole bunch. They usually begin with WS_. <Check the on-line help on WS_*** and/or CreateWindow (end of listing)>. STATIC CONTROLS-- -Used to display text, icons, or rectangles. -We don't need to worry about repainting in response to WM_PAINT messages. -Often act as labels for other controls. -Text can be changed by sending the control a message. Static Control Styles--There are lots, see on-line help (CreateWindow, end of listing). The STATIC Example Program: displays 3 static controls (text,icon,rectangle)-- When the main program's window is created, Windows sends WM_CREATE message to the WndProc(). The program responds to this message by creating the controls with CreateWindow(). Creation of the static control--All use the WS_CHILD style, so the control is a child window, attached to parent and only visible if parent is. It can only exist inside parent's client area. Individual styles: SS_CENTER for text==>text is centered; text wraps to next line as necessary. SS_ICON for icon: Uses the same icon as the program icon (but it could be anything). The .ico file name is declared in the resource (.rc) file as follows: MYICON ICON static.ico So we must use "MYICON" as the 2nd parameter in CreateWindow(). The height and width parameters are set to 0, ==>icon size is determined by the icon resource data. SS_GRAYRECT for a rectangle whose color is gray Getting the instance handle (hInstance): Three ways-- 1. GetWindowLong(hWnd, nOffset)--retrieves a word at a specified offset in some extra memory that Windows maintains for the window whose handle is hWnd. GWL_HINSTANCE is the offset of the word containing the handle of the module that owns the window. So a call using this value will return the hInstance of the program creating the window. 2. Use a global variable and set it equal to hInstance after creating the main window in WinMain(). 3. Use the fact that during a WM_CREATE message, the lParam is a pointer to a structure of type CREATESTRUCT that has an hInstance member. So just cast the lParam into a long pointer to that type of structure: CreateWindow(..., ((LPCREATESTRUCT)lParam -> hInstance,...); Sending a Message to a Control-- In spite of the "static" name, we can change what's displayed in a STATIC control. This is done in two ways in STATIC.CPP using the menu items: "Change Title" and "Send Message". The user selects a menu item ==> Windows sends a WM_COMMAND message with LOWORD(wParam) equal to menu the item ID. In the .rc file the menu items are: "&Change Title" -- ID = IDM_CHANGE "&Send Message" -- ID = IDM_SEND "&Quit" -- ID = IDM_QUIT The values of these IDs are given in the .h file. So our program can test the low word of the wParam for the menu item selected: case WM_COMMAND: switch (LOWORD(wParam)) /* menu items */ { case IDM_CHANGE: /* change static text */ SetWindowText (hStaticText, "New Text Via SetWindowText().") ; break ; case IDM_SEND: /* change static text */ SendMessage (hStaticText, WM_SETTEXT, 0, (LPARAM) "New Text Via SendMessage().") ; break ; case IDM_QUIT: DestroyWindow (hWnd) ; /* destroy window, */ break ; /* terminating application */ } break ; Note the use of the functions: SetWindowText()-- changes the test displayed in the specified window or control. (Remember that a static control is a window even if it doesn't look like one.) For a main window, it sets the window's title to the specified text. For a static control containing text, the text is changed. The parameters are: A handle to the window in which the text is to be displayed; A pointer to the string to be displayed. SendMessage()--sends a message directly to the WinProc() of a window. This doesn't return until the message has been processed. The parameters are: the handle of destination window; the ID of the message to send; the wParam and lParam values containing message data, if any. Here the message is WM_SETTEXT, sent to the static control. When this is received, the static control's WndProc() code causes it to change its text string. For this message,: -the wParam must be 0; -the lParam is a LPSTR pointing to the text string to be displayed. But SendMessage() requires an LPARAM type--hence the cast: (LPARAM) "New text......" We could use wsprintf() here to generate a formatted text string: wsprintf(cBuf, format string, values); For example, see STATIC1.CPP which outputs a new test number each time the "Send Message" menu item is selected: static char cBuf[50]; static int nn = 0; ... wsprintf(cBuf, "This is test # %d ", nn); nn++; SendMessage(hStaticText, WM_SETTEXT, 0, (LPARAM) cBuf); This will cause the value of nn to be displayed as part of the string. Note that since nn is declared as static, the value is "remembered" each time the user clicks on the menu item that causes the message to be sent to the static control. Therefore the value displayed increments by one each time. A related message-sending function: PostMessage()--places (posts) a message in the window's message queue and returns immediately. This could have been used here. Messages can be retrieved with GetMessage() or PeekMessage(), as we've seen. BUTTON CONTROLS-- Button controls are little (usually) windows that can be clicked by the user to indicate actions to be taken or choices made. There are lots of different styles (e.g., check, radio, group) available. Typically buttons notify the parent window when the user chooses the control. When creating buttons with CreateWindow(), the style parameter can have any BS_ values (Check the on-line help at end of CreateWindow listing). When a control is selected with the mouse, Windows sends a WM_COMMAND message to control's parent window; the control's ID value is placed in the low order word of the wParam of the message, so we can do a switch/case on LOWORD(wParam) to find out which button it was. Recall that WM_COMMAND messages are also sent for menu items; the wParam is the menu item's ID value. It's important to have distinct numbers for menu items and controls so the WinProc() can determine where the WM_COMMAND message came from (menu or control). WM_COMMAND message details for buttons (and other child window controls)-- User interacts with the button (or other control) ==> WM_COMMAND message is sent to the parent window LOWORD(wParam) = buttonID (hMenu value when button was created) lParam = handle to the button's window HIWORD(wParam) = button notification code (BN_***) Indicates what the user interaction was (See on-line help on BN_ ). <The details here have changed from Win16> Example program: BUTTON.CPP illustrates many button styles-- Two radio buttons inside a group box; a check box; two pushbuttons. Pushbuttons (command buttons)--initiate some action when selected. Others allow the user to make selections. Radio buttons should not be selected simultaneously. So selecting Radio 1 should automatically deselect Radio 2 and vice-versa. Check box--We want it to be a three-state button: on (checked), off (open), disabled (grayed). It cycles between the three states on each click, so the style should be BS_AUTO3STATE. All buttons are created in response to the WM_CREATE message with CreateWindow(), as in STATIC.CPP. All have the WS_CHILD style. The radio buttons are positioned inside a Group Box. The buttons are displayed with ShowWindow(). The Control IDs are constants defined in BUTTON.H: The control ID is used in each call to CreateWindow() as the hMenu parameter. For main program windows, hMenu is used to pass a handle to the menu definition. For window controls, hMenu used to pass the control's ID (there's no room for a menu on a control). It must be cast to (HMENU). This is how Windows determines the ID value of a selected control button. (The group box can't be selected, so its ID is set to NULL.) Processing Button Control Message in BUTTON.CPP's WndProc()-- The Boolean variables bIsChecked and bTopRadioOn keep track of the status of the check box and the top radio button. If "Radio1" or "Radio2" is selected, the program sends a BM_SETCHECK message to the radio button with SendMessage(). The lParam is the check state (TRUE or FALSE). The program logic makes the buttons mutually exclusive. This must be done in the code if the button doesn't have an AUTO style. Note that the check box code doesn't need to have this logic since its style is BS_AUTO3STATE. Thus no processing of the WM_COMMAND is necessary for this button. "Show status" button: Windows sends program a WM_COMMAND message with LOWORD(wParam) set to button's ID value (STATUS_BUTTON). The WinProc() responds by using SendMessage() to send BM_GETCHECK messages to the check box and RADIO1 button. The value returned indicates the status of the button (0=unchecked/open, 1=checked/filled, 2=grayed). The program doesn't check the other radio button, since its logic makes sure they're in the opposite state always. Status values are finally displayed in a small popup window using MessageBox(). The parameters are: the handle of parent window of the message box, a LPSZ pointing to the text to be displayed in the message box, a LPSZ pointing to the text to appear in the message box's title, the style of the message box--MB_OK ==> it contains an OK push button. The message box remains on the screen until the user presses the OK button. wsprintf()--Windows equivalent of the sprintf() C library function. It copies a formatted string to a character buffer. sprintf() could be used, but the standard C code would have to be added to your Windows program by the compiler/linker. wsprintf() is a part of Windows, so the program is smaller using it. We want the "Test Settings" menu item to do the same thing as the "Show Status" button. To avoid using a GO TO, the program uses SendMessage() to simulate the WM_COMMAND message that Windows sends when the "Show Status" button is pressed. In other words, the program sends a message to itself to imitate what Windows does, so the window handle is that of the main program window. The wParam message data is STATUS_BUTTON, so when the WinProc() receives it, it executes the same STATUS_BUTTON code that was executed when the "Show Status" button is pressed. LIST BOX-- A list box contains lists of items that can be selected. The entire list is shown. (Many styles provide scroll bars as needed.) It is created with CreateWindow() using the "LISTBOX" window class. There are many possible values of the dwStyle parameter (see on-line help on LBS_). Include the LBS_NOTIFY or LBS_STANDARD style if you want the list box to send messages to the parent window when it's activated by the user. Using a LISTBOX is similar to using button controls. The program communicates with the LISTBOX by sending it messages; the LISTBOX notifies the program if the user makes a selection by sending a WM_COMMAND message. The LISTBOX.CPP Example Program-- Creates an empty LISTBOX. Selecting the "Fill Listbox" menu item causes the list box to be filled with some strings. Selecting any string in the List box brings up a message box to display the chosen string. WM_CREATE-- Creates an empty List box of style WS_CHILD|LBS_STANDARD, so there will be a child window List box with standard attributes: entries sorted in ASCII order and with a vertical scroll bar on the right side if all entries can't be seen. The ID value (LISTBOX_ID defined in LISTBOX.H) is stored in the hMenu variable as in BUTTON.CPP. Filling the box--The user selects menu item "Fill Listbox" (ID value IDM_FILL as specified in the .rc and .h files) ---> WM_COMMAND message with wParam set to IDM_FILL. The WndProc() responds by sending messages to the List box to empty itself (LB_RESETCONTENT) and to add several strings to the list using LB_ADDSTRING message ID. Again, note the type casting of the string as in STATIC.CPP. If the user clicks in any part of the list box, Windows sends the program a WM_COMMAND message with the List box's ID number in LOWORD(wParam) and the list box window handle in the lParam. If the click took place over one of the selections in the list box, a list box notification code of LBN_SELCHANGE is placed in HIWORD(wParam). (See on-line help; search on LBN_ for the various list box notification codes.) We can then send the list box an LB_GETCURSEL message to obtain the item number (0, 1, 2,...) of the selection. Then we send a LB_GETTEXT msg to the list box with the item number in the wParam. Windows will copy the string into the character buffer pointed to by the lParam. This has to be cast to an LPARAM. Finally we can format the string with wsprintf() and display it in a message box as in the BUTTON.CPP program. COMBO BOX-- A combo box is just like a list box, but it has an edit header control that shows the current selection. The combo box can be set up to show only the header, so it takes up less space. Styles--(see on-line help on CBS_) The logic of the COMBO.CPP program is very similar to LISTBOX.CPP (LB --> CB, LISTBOX --> COMBOBOX) Note that to make the combo box's list box initially visible, as in COMBO.CPP, use the style CBS_SIMPLE in the call to CreateWindow(); to make it initially hidden, use the style CBS_DROPDOWN. SCROLLBAR-- There are two types of scroll bars: 1. A control attached to the edge of a parent window. It allows the user to "scroll" the information in the parent window's client area. 2. A stand-alone child window control that can allow the user to enter an integer value by moving the scroll bar "thumb". Both types send messages when the user interacts with the scroll bar. The messages are: WM_HSCROLL or WM_VSCROLL for horizontal/vertical scroll bars. For stand-alone scrollbars, the lParam contains the window handle of the scroll bar control; for an attached scroll bar it is 0. The low word of the wParam is a notification code number that describes what action the user has taken. Scroll bar notification codes [LOWORD(wParam)] values: SB_THUMBTRACK (pressed), SB_THUMBPOSITION (released), SB_LINEUP, SB_PAGEUP, SB_LINEDOWN, SB_PAGEDOWN. If the notification code is SB_THUMBTRACK or SB_THUMBPOSITION, the high word of the wParam is the current position of the scrollbar's thumb. For a horizontal scroll bar, "up" means left and "down" means right. Scrollbar styles when creating the scrollbar--See online help on SBS_. The default alignment for an attached scroll bar attaches it to the right side and bottom of a window. A program can also move the scroll bar's thumb, change the range of values represented by the scroll bar, and determine the current thumb position by using the functions: GetScrollPos() -Retrieve current position of thumb GetScrollRange() -Retrieve minimum and maximum value range SetScollPos() -Set position of thumb SetScrollRange -Set minimum and maximum value range ShowScrollBar() -Display scroll bar, optionally attaching to window border The SCROLL1.CPP Example (a Stand-alone Scrollbar)-- Allows the user to enter an integer value between 0 and 50 by using a stand- alone scroll bar. The current value is continually displayed in a static control. An informational message box shows the current value when the user chooses the menu item "Get Value". WM_CREATE message: 1. Use CreateWindow() to create the scroll bar. 2. Set the Upper/lower number range values-- Use SetScrollRange(hScroll,SB_CTL,0,MAXSCROLL,FALSE); The SB_CTL parameter is a flag indicating that the first parameter is a handle to a stand-alone scroll bar (not to a window). [For windows with h/v scroll bars, SB_HORZ and SB_VERT specify which scroll bar we want.] MAXSCROLL is set to 50 in the .h file, and FALSE means we don't want to redraw the scroll bar to reflect the change. 3. We use the static int nScrollPos variable for the program to keep track of the current position of the thumb. (This must be static so that the former value is remembered.) To set the initial position of the thumb to 1/2 range we use SetScrollPos(). 4. Use ShowScrollBar() to display the scroll bar. 5. Use CreateWindow() to create a static control to hold the thumb position. 6. Use wsprintf(), SetWindowText(), and ShowWindow() to display the current scroll position in the static control. WM_HSCROLL message: This is sent if the user clicks any part of the scroll bar. A notification code is passed in LOWORD(wParam). This specifies what part of scroll bar was clicked-- Thumb dragged==>SB_THUMPOSITION [position value in HIWORD(wParam)] Bottom (right) arrow clicked==>SB_LINEDOWN, so increment position by 1 Top (left) arrow clicked==> SB_LINEUP, so decrement position by 1 Bottom (right) area clicked==>SB_PAGEDOWN, so increment position by 10 Top (left) area clicked==> SB_PAGEUP, so decrement position by 10 (In each case nothing is done if the new position is outside of the range) For the last four cases, the program must use SetScrollPos() to reposition the thumb. Finally, the value of nScrollPos is output to the static control with SetWindowText(). If the user clicks on "Get Value" menu item, WM_COMMAND with wParam==IDM_GET is sent to the program. We use GetWindowText() to get the contents of the static control, wsprintf() to format the number, and MessageBox() to output the result in a message box window. Here the last parameter indicates a style of message box that has an OK button and an informational "i" icon along side the message (MB_OK | MB_ICONINFORMATION). Scroll Bars Attached to a Window (The SCROLL2.CPP Example)-- This creates a window with a vertical scroll bar and puts 3 lines of text in the client area; the user can scroll through the client area using the scroll bar. To add scroll bars to the edge of a main window, use WS_HSCROLL or WS_VSCROLL as the style when creating the window. e.g., hWnd = CreateWindow ("MyClass", "Scroll Control Example 2", WS_OVERLAPPEDWINDOW | WS_VSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; The program creates this type of main window, and keeps track of the position with nScrollPos variable as in SCROLL1.CPP. The value of nScrollPos is used to place lines of text in the window when it's exposed. The higher the value, the lower the position in the window the text will be painted. When the scroll bar is moved, the client area of the window must be repainted so the text will appear in the correct position. So somehow we need to convince Windows to send a WM_PAINT message. We use InvalidateRect(), which tells Windows that the client area needs to be repainted. Windows responds by sending a WM_PAINT msg. InvalidateRect(HWND hwnd, const RECT FAR* lprc, BOOL fErase); The parameters are: -hwnd: window with client area that needs to be repainted. -lprc: address of a rectangle structure containing the coordinates of the rectangle to be updated (NULL ==> entire client area). -fErase indicates if the background within the update area is to be erased when BeginPaint() is called. When the program gets the WM_PAINT msg, it paints the text lines at their new position. This gives the illusion that the lines moved down. [This is the opposite from the way scrolling normally works!] EDIT CONTROLS (for viewing and editing text)-- These can range from a small rectangle for entering a single word or number up to a window that occupies the parent window's whole client area. Key word processing features are built into edit controls: -The current location is kept track of with a "carat"--a small vertical line -Backspace, Delete, and arrow keys are recognized and give expected responses -Blocks of text can be marked for deletion/cutting/pasting, using the clipboard -IF WS_HSCROLL, WS_VSCROLL styles are used, the user can scroll through the text; the program doesn't have to worry about repainting when scrolling occurs. Edit Controls do NOT have the ability to format text with different fonts, character styles, etc. Styles of EDIT windows -- See the online help on edit styles. The EDIT1.CPP Example -- This program displays multiline edit control box inside the client area. The user can enter/delete text in the edit control. Scrolling is built in. Clicking on the menu item "Get Text" causes the contents of the edit control to be displayed in the main window's client area. An EDIT class child window is created with CreateWindow() in response to the WM_CREATE message. The style is: WS_CHILD | ES_MULTILINE | WS_VISIBLE | ES_AUTOSCROLL | WS_BORDER (so it has visible border) Note it has a null ("") title string, since we don't want it to contain any text initially. The program communicates with the control by sending/receiving messages. The text in an edit control is stored as one long character string. Each carriage return <CR> is stored as ASCII code (0x0D,0x0A). If a line wraps because the next word won't fit on the same line, a <CR> is inserted. A NULL character is inserted only at the end of the last line of text. Getting the contents of an edit control--We must extract it line by line. To do this: 1. Send the control an EM_GETLINECOUNT message to determine the number of lines stored. 2. For each line, send the following messages: EM_GETLINE to copy the line into a buffer; EM_LINEINDEX to determine character position of the start of the next line; EM_LINELENGTH to get the length of the line; All this is done in response to a WM_COMMAND message when the user selects the menu item "Get Text" (IDM_GET). The extracted text is then written line by line to the main window using TextOut() and the line number to position it. Note that if we resize or cover/uncover part of the text extracted and displayed in the main window, it does not reappear, since we're not handling WM_PAINT messages for the main window. The text in the edit window does reappear because that handling is built into the edit control.