Programming Windows with MFC, Second Edition
Windows makes the classic controls available to the application programs that it hosts by registering six predefined WNDCLASSes. The control types, their WNDCLASSes, and the corresponding MFC classes are shown in the following table.
The Classic Controls
Control Type | WNDCLASS | MFC Class |
---|---|---|
Buttons | "BUTTON" | CButton |
List boxes | "LISTBOX" | CListBox |
Edit controls | "EDIT" | CEdit |
Combo boxes | "COMBOBOX" | CComboBox |
Scroll bars | "SCROLLBAR" | CScrollBar |
Static controls | "STATIC" | CStatic |
A control is created by instantiating one of the MFC control classes and calling the resulting object's Create function. If m_wndPushButton is a CButton object, the statement
m_wndPushButton.Create (_T ("Start"), WS_CHILD ¦ WS_VISIBLE ¦ BS_PUSHBUTTON, rect, this, IDC_BUTTON); |
creates a push button control labeled "Start." The first parameter is the text that appears on the button face. The second is the button style, which is a combination of conventional (WS_) window styles and button-specific (BS_) window styles. Together, WS_CHILD, WS_VISIBLE, and BS_PUSHBUTTON create a push button control that is a child of the window identified in the fourth parameter and that is visible on the screen. (If you omit WS_VISIBLE from the window style, the control won't become visible until you call ShowWindow on it.). rect is a RECT structure or a CRect object specifying the control's size and location, in pixels, relative to the upper left corner of its parent's client area. this identifies the parent window, and IDC_BUTTON is an integer value that identifies the control. This value is also known as the child window ID or control ID. It's important to assign a unique ID to each control you create within a given window so that you can map the control's notification messages to member functions in the parent window class.
List boxes and edit controls assume a "flat" look when they're created with Create. To endow them with the contemporary chiseled look that most users have grown accustomed to (Figure 7-1), you need to create list boxes and edit controls with CreateEx instead of Create and include a WS_EX_CLIENTEDGE flag in the extended style specified in the function's first parameter. If m_wndListBox is a CListBox object, the following statement creates a list box with chiseled edges and parents it to the window identified by the this pointer:
m_wndListBox.CreateEx (WS_EX_CLIENTEDGE, _T ("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | LBS_STANDARD, rect, this, IDC_LISTBOX); |
As an alternative, you can derive your own class from CListBox, override PreCreateWindow in the derived class, and apply WS_EX_CLIENTEDGE to the window style in the PreCreateWindow, as demonstrated here:
BOOL CMyListBox::PreCreateWindow (CREATESTRUCT& cs) { if (!CListBox::PreCreateWindow (cs)) return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE; |
With PreCreateWindow implemented like this, a CMyListBox object will have chiseled borders regardless of how it's created.
Figure 7-1. A list box with flat edges (left) and chiseled edges (right).
A control sends notifications to its parent in the form of WM_COMMAND messages. The kinds of notifications that are sent vary with the control type, but in each case, information encoded in the message's wParam and lParam parameters identifies the control that sent the message and the action that prompted the message. For example, the WM_COMMAND message sent when a push button is clicked contains the notification code BN_CLICKED in the upper 16 bits of wParam, the control ID in the lower 16 bits of wParam, and the control's window handle in lParam.
Rather than process raw WM_COMMAND messages, most MFC applications use message maps to link control notifications to class member functions. For example, the following message-map entry maps clicks of the push button whose control ID is IDC_BUTTON to the member function OnButtonClicked:
ON_BN_CLICKED (IDC_BUTTON, OnButtonClicked) |
ON_BN_CLICKED is one of several control-related message-map macros that MFC provides. For example, there are ON_EN macros for edit controls and ON_LBN macros for list box controls. There's also the generic ON_CONTROL macro, which handles all notifications and all control types, and ON_CONTROL_RANGE, which maps identical notifications from two or more controls to a common notification handler.
Controls send messages to their parents, but it's no less common for parents to send messages to controls. For example, a check mark is placed in a check box control by sending the control a BM_SETCHECK message with wParam equal to BST_CHECKED. MFC simplifies message-based control interfaces by building member functions into its control classes that wrap BM_SETCHECK and other control messages. For example, the statement
m_wndCheckBox.SetCheck (BST_CHECKED); |
places a check mark inside a check box represented by a CButton object named m_wndCheckBox.
Because a control is a window, some of the member functions that the control classes inherit from CWnd are useful for control programming. For example, the same SetWindowText function that changes the text in a window's title bar inserts text into an edit control, too. Other useful CWnd functions include GetWindowText, which retrieves text from a control; EnableWindow, which enables and disables a control; and SetFont, which changes a control's font. If you want to do something to a control and can't find an appropriate member function in the control class, check CWnd's list of member functions. You'll probably find the one you're looking for.
The CButton Class
CButton represents button controls based on the "BUTTON" WNDCLASS. Button controls come in four flavors: push buttons, check boxes, radio buttons, and group boxes. All four button types are shown in Figure 7-2.
Figure 7-2. The four types of button controls.
When you create a button control, you specify which of the four button types you want to create by including one of the following flags in the button's window style:
Style | Description |
---|---|
BS_PUSHBUTTON | Creates a standard push button control |
BS_DEFPUSHBUTTON | Creates a default push button; used in dialog boxes to identify the push button that's clicked if Enter is pressed |
BS_CHECKBOX | Creates a check box control |
BS_AUTOCHECKBOX | Creates a check box control that checks and unchecks itself when clicked |
BS_3STATE | Creates a three-state check box control |
BS_AUTO3STATE | Creates a three-state check box control that cycles through three states—checked, unchecked, and indeterminate—when clicked |
BS_RADIOBUTTON | Creates a radio button control |
BS_AUTORADIOBUTTON | Creates a radio button control that, when clicked, checks itself and unchecks other radio buttons in the group |
BS_GROUPBOX | Creates a group box control |
In addition, you can OR one or more of the following values into the window style to control the alignment of the text on the button face:
Style | Description |
---|---|
BS_LEFTTEXT | Moves the text accompanying a radio button or check box control from the button's right (the default) to its left |
BS_RIGHTBUTTON | Same as BS_LEFTTEXT |
BS_LEFT | Left justifies the button text in the control rectangle |
BS_CENTER | Centers the button text in the control rectangle |
BS_RIGHT | Right justifies the button text in the control rectangle |
BS_TOP | Positions the button text at the top of the control rectangle |
BS_VCENTER | Positions the button text in the center of the control rectangle vertically |
BS_BOTTOM | Positions the button text at the bottom of the control rectangle |
BS_MULTILINE | Allows text too long to fit on one line to be broken into two or more lines |
There are other button styles, but most of them are rarely used. For example, BS_NOTIFY programs a button to send BN_DOUBLECLICKED, BN_KILLFOCUS, and BN_SETFOCUS notifications. BS_OWNERDRAW creates an owner-draw button—one whose appearance is maintained by the button's parent rather than the button itself. Owner-draw buttons have been largely superseded by bitmap buttons and icon buttons. You'll learn more about bitmap buttons and icon buttons later in this chapter.
Push Buttons
A push button is a button control created with the style BS_PUSHBUTTON. When clicked, a push button control sends its parent a BN_CLICKED notification encapsulated in a WM_COMMAND message. Absent the button style BS_NOTIFY, a push button sends no other types of notifications.
MFC's ON_BN_CLICKED macro links BN_CLICKED notifications to member functions in the parent window class. The message-map entry
ON_BN_CLICKED (IDC_BUTTON, OnButtonClicked) |
connects OnButtonClicked to clicks of the push button whose control ID is IDC_BUTTON. A trivial implementation of OnButtonClicked looks like this:
void CMainWindow::OnButtonClicked () { MessageBox (_T ("I've been clicked!")); } |
Like command handlers for menu items, BN_CLICKED handlers accept no parameters and return no values.
Check Boxes
Check boxes are buttons created with the style BS_CHECKBOX, BS_AUTOCHECKBOX, BS_3STATE, or BS_AUTO3STATE. BS_CHECKBOX and BS_AUTOCHECKBOX check boxes can assume two states: checked and unchecked. A check box is checked and unchecked with CButton::SetCheck:
m_wndCheckBox.SetCheck (BST_CHECKED); // Check m_wndCheckBox.SetCheck (BST_UNCHECKED); // Uncheck |
To find out whether a check box is checked, use CButton::GetCheck. A return value equal to BST_CHECKED means the box is checked. BST_UNCHECKED means it's not.
Like push button controls, check boxes send BN_CLICKED notifications to their parents when clicked. The check mark in a BS_AUTOCHECKBOX check box toggles on and off automatically in response to button clicks. The check mark in a BS_CHECKBOX check box doesn't. Therefore, BS_CHECKBOX-style check boxes are of little use unless you write BN_CLICKED handlers to go with them. The following BN_CLICKED handler toggles m_wndCheckBox's check mark on and off:
void CMainWindow::OnCheckBoxClicked () { m_wndCheckBox.SetCheck (m_wndCheckBox.GetCheck () == BST_CHECKED ? BST_UNCHECKED : BST_CHECKED); } |
The BS_3STATE and BS_AUTO3STATE button styles create a check box that can assume a third state in addition to the checked and unchecked states. The third state is called the indeterminate state and is entered when the user clicks a BS_AUTO3STATE check box that is currently checked or when SetCheck is called with a BST_INDETERMINATE parameter:
m_wndCheckBox.SetCheck (BST_INDETERMINATE); |
An indeterminate check box contains a grayed check mark. The indeterminate state is used to indicate that something is neither wholly on nor wholly off. For example, a word processing program might set a check box labeled "Bold" to the indeterminate state when the user selects a mix of normal and boldface text.
Radio Buttons
A radio button is a button control with the style BS_RADIOBUTTON or BS_AUTORADIOBUTTON. Radio buttons normally come in groups, with each button representing one in a list of mutually exclusive options. When clicked, a BS_AUTORADIOBUTTON radio button checks itself and unchecks the other radio buttons in the group. If you use the BS_RADIOBUTTON style instead, it's up to you to do the checking and unchecking using CButton::SetCheck.
Radio buttons send BN_CLICKED notifications to their parents, just as push buttons and check boxes do. The following BN_CLICKED handler checks the m_wndRadioButton1 radio button and unchecks three other radio buttons in the same group:
void CMainWindow::OnRadioButton1Clicked () { m_wndRadioButton1.SetCheck (BST_CHECKED); m_wndRadioButton2.SetCheck (BST_UNCHECKED); m_wndRadioButton3.SetCheck (BST_UNCHECKED); m_wndRadioButton4.SetCheck (BST_UNCHECKED); } |
Unchecking the other radio buttons maintains the exclusivity of the selection. A BN_CLICKED handler isn't necessary for BS_AUTORADIOBUTTON radio buttons, though you can still provide one if you want to respond to changes in a radio button's state at the instant the button is clicked.
For BS_AUTORADIOBUTTON radio buttons to properly deselect the other buttons in the group, you must group the buttons so that Windows knows which buttons belong to the group. To create a group of BS_AUTORADIOBUTTON radio buttons, follow this procedure:
- In your application's code, create the buttons in sequence, one after another; don't create any other controls in between.
- To mark the beginning of the group, assign the style WS_GROUP to the first radio button you create.
- If you create additional controls after the last radio button is created, assign the WS_GROUP style to the first additional control that you create. This implicitly marks the previous control (the last radio button) as the final one in the group. If there are no other controls after the radio buttons but there are other controls in the window, mark the first control with WS_GROUP to prevent the radio button group from wrapping around.
The following example demonstrates how to create four BS_AUTORADIOBUTTON radio buttons belonging to one group and three belonging to another group, with a check box control in between:
m_wndRadioButton1.Create (_T ("COM1"), WS_CHILD ¦ WS_VISIBLE ¦ WS_GROUP ¦ BS_AUTORADIOBUTTON, rect1, this, IDC_COM1); m_wndRadioButton2.Create (_T ("COM2"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTORADIOBUTTON, rect2, this, IDC_COM2); m_wndRadioButton3.Create (_T ("COM3"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTORADIOBUTTON, rect3, this, IDC_COM3); m_wndRadioButton4.Create (_T ("COM4"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTORADIOBUTTON, rect4, this, IDC_COM4); m_wndRadioButton1.SetCheck (BST_CHECKED); m_wndCheckBox.Create (_T ("Save settings on exit"), WS_CHILD ¦ WS_VISIBLE ¦ WS_GROUP ¦ BS_AUTOCHECKBOX, rectCheckBox, this, IDC_SAVESETTINGS); m_wndRadioButton5.Create (_T ("9600"), WS_CHILD ¦ WS_VISIBLE ¦ WS_GROUP ¦ BS_AUTORADIOBUTTON, rect5, this, IDC_9600); m_wndRadioButton6.Create (_T ("14400"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTORADIOBUTTON, rect6, this, IDC_14400); m_wndRadioButton7.Create (_T ("28800"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTORADIOBUTTON, rect7, this, IDC_28800); m_wndRadioButton5.SetCheck (BST_CHECKED); |
Because of the BS_AUTORADIOBUTTON styles and the logical grouping provided by the WS_GROUP bits, checking any of the first four radio buttons automatically unchecks the other three in the group, and checking any radio button in the second group automatically unchecks the other two.
For good form, the code above calls SetCheck to check a button in each group. One of the buttons in a group of radio buttons should always be checked, even if the user has yet to provide any input. Radio buttons are never checked by default, so it's your responsibility to do the initializing.
Group Boxes
A group box is a button control created with the style BS_GROUPBOX. It is unlike other button controls in that it never receives the input focus and never sends notifications to its parent.
The sole function of the group box is to visually delineate control groups. Enclosing groups of controls in group boxes makes it apparent to the user which controls go together. Group boxes have nothing to do with the logical grouping of controls, so don't expect a series of radio buttons to function as a group simply because there's a group box around them.
The CListBox Class
MFC's CListBox class encapsulates list box controls, which display lists of text strings called items. A list box optionally sorts the items that are added to it, and scrolling is built in so that the number of items a list box can display isn't limited by the physical dimensions of the list box window.
List boxes are extremely useful for presenting lists of information and allowing users to select items from those lists. When an item is clicked or double-clicked, most list boxes (technically, those with LBS_NOTIFY in their window styles) notify their parents with WM_COMMAND messages. MFC simplifies the processing of these messages by providing ON_LBN message-map macros that you can use to route list box notifications to handling functions in the parent window class.
A standard list box displays text strings in a vertical column and allows only one item to be selected at a time. The currently selected item is highlighted with the system color COLOR_HIGHLIGHT. Windows supports a number of variations on the standard list box, including multiple-selection list boxes, multicolumn list boxes, and owner-draw list boxes that display images instead of text.
Creating a List Box
The following statement creates a standard list box from a CListBox object named m_wndListBox:
m_wndListBox.Create (WS_CHILD ¦ WS_VISIBLE ¦ LBS_STANDARD, rect, this, IDC_LISTBOX); |
LBS_STANDARD combines the styles WS_BORDER, WS_VSCROLL, LBS_NOTIFY, and LBS_SORT to create a list box that has a border and a vertical scroll bar, that notifies its parent when the selection changes or an item is double-clicked, and that alphabetically sorts the strings that are added to it. By default, the scroll bar is visible only when the number of items in the list box exceeds the number that can be displayed. To make the scroll bar visible at all times, include the style LBS_DISABLENOSCROLL. A list box doesn't have a vertical scroll bar unless the style WS_VSCROLL or LBS_STANDARD is included. Similarly, it doesn't have a border unless it is created with the style WS_BORDER or LBS_STANDARD. You might want to omit the border if you create a list box that encompasses the entire client area of its parent. These and other styles used to customize a list box's appearance and behavior are summarized in the table below.
List boxes have keyboard interfaces built in. When a single-selection list box has the input focus, the up arrow, down arrow, Page Up, Page Down, Home, and End keys move the highlighted bar identifying the current selection. In addition, pressing a character key moves the selection to the next item beginning with that character. Keyboard input works in multiple-selection list boxes, too, but it's the position of a dotted focus rectangle, not the selection, that changes. Pressing the spacebar toggles the selection state of the item with the focus in a multiple-selection list box.
You can customize a list box's keyboard interface by including the LBS_WANTKEYBOARDINPUT style and processing WM_VKEYTOITEM and WM_CHARTOITEM messages. An MFC application can map these messages to OnVKeyToItem and OnCharToItem handlers using the ON_WM_VKEYTOITEM and ON_WM_CHARTOITEM macros. A derived list box class can handle these messages itself by overriding the virtual CListBox::VKeyToItem and CListBox::CharToItem functions. One use for this capability is to create a self-contained list box class that responds to presses of Ctrl-D by deleting the item that is currently selected.
List Box Styles
Style | Description |
---|---|
LBS_STANDARD | Creates a "standard" list box that has a border and a vertical scroll bar, notifies its parent window when the selection changes or an item is double-clicked, and sorts items alphabetically. |
LBS_SORT | Sorts items that are added to the list box. |
LBS_NOSEL | Creates a list box whose items can be viewed but not selected. |
LBS_NOTIFY | Creates a list box that notifies its parent when the selection changes or an item is double-clicked. |
LBS_DISABLENOSC ROLL | Disables the list box's scroll bar when it isn't needed. Without this style, an unneeded scroll bar is hidden rather than disabled. |
LBS_MULTIPLESEL | Creates a multiple-selection list box. |
LBS_EXTENDEDSEL | Adds extended selection support to a multiple-selection list box. |
LBS_MULTICOLUMN | Creates a multicolumn list box. |
LBS_OWNERDRAWVARIABLE | Creates an owner-draw list box whose items can vary in height. |
LBS_OWNERDRAWFIXED | Creates an owner-draw list box whose items are the same height. |
LBS_USETABSTOPS | Configures the list box to expand tab characters in item text. |
LBS_NOREDRAW | Creates a list box that doesn't automatically redraw itself when an item is added or removed. |
LBS_HASSTRINGS | Creates a list box that "remembers" the strings added to it. Conventional list boxes have this style by default; owner-draw list boxes don't. |
LBS_WANTKEYBOARDINPUT | Creates a list box that sends its parent a WM_VKEYTOITEM or WM_CHARTOITEM message when a key is pressed. This style is used to customize the list box's response to keyboard input. |
LBS_NOINTEGRALHEIGHT | Allows a list box to assume any height. By default, Windows sets a list box's height to a multiple of the item height to prevent items from being partially clipped. |
Because the default font that Windows uses for list boxes is proportionally spaced, it is virtually impossible to line up columns of information in a list box by separating them with space characters. One way to create a columnar list box display is to use SetFont to apply a fixed-pitch font to the list box. A better solution is to assign the list box the style LBS_USETABSTOPS and separate columns of information with tab characters. An LBS_USETABSTOPS list box treats tab characters the way a word processor does, automatically advancing to the next tab stop when a tab character is encountered. By default, tab stops are evenly spaced about eight character widths apart. You can change the default tab stop settings with the CListBox::SetTabStops function. SetTabStops measures distances in dialog units. One dialog unit is approximately equal to one-fourth the width of a character in the system font. The statement
m_wndListBox.SetTabStops (64); |
sets the space between tab stops to 64 dialog units, and
int nTabStops[] = { 32, 48, 64, 128 }; m_wndListBox.SetTabStops (4, nTabStops); |
places tab stops at 32, 48, 64, and 128 dialog units from the left margin.
By default, a list box repaints itself whenever an item is added or removed. Usually that's just what you want, but if you're adding hundreds or perhaps thousands of items in rapid-fire fashion, the repeated repaints produce an unsightly flashing effect and slow down the insertion process. You can use LBS_NOREDRAW to create a list box that doesn't automatically repaint itself. Such a list box will be repainted only when its client area is invalidated.
An alternative to using LBS_NOREDRAW is to disable redraws before beginning a lengthy insertion process and to reenable them after the last item is inserted. You can enable and disable redraws programmatically by sending a list box WM_SETREDRAW messages, as shown here:
m_wndListBox.SendMessage (WM_SETREDRAW, FALSE, 0); // Disable redraws. |
A list box is automatically repainted when redraws are enabled with WM_SETREDRAW, so it's not necessary to follow up with a call to Invalidate.
Unless a list box is created with the style LBS_MULTIPLESEL, only one item can be selected at a time. In a single-selection list box, clicking an unselected item both selects that item and deselects the one that was formerly selected. In a multiple-selection list box, any number of items can be selected. Most multiple-selection list boxes are also created with the style LBS_EXTENDEDSEL, which enables extended selections. In an extended-selection list box, the user selects the first item by clicking it and selects subsequent items by clicking with the Ctrl key pressed. In addition, the user can select entire ranges of contiguous items by clicking the first item in the range and then clicking the last item in the range with the Shift key held down. The Ctrl and Shift keys can be combined to select multiple items and ranges, the net result being a handy interface for selecting arbitrary combinations of items.
The LBS_MULTICOLUMN style creates a multicolumn list box. Multicolumn list boxes are usually created with the WS_HSCROLL style so that their contents can be scrolled horizontally if not all the items can be displayed at once. (Multicolumn list boxes can't be scrolled vertically.) You can adjust the column width with the CListBox::SetColumnWidth function. Normally, the column width should be based on the average width of a character in the list box font. The default column width is enough to display about 16 characters in the default list box font, so if you'll be inserting strings longer than that, you should expand the column width to prevent columns from overlapping.
Adding and Removing Items
A list box is empty until items are added to it. Items are added with CListBox::AddString and CListBox::InsertString. The statement
m_wndListBox.AddString (string); |
adds the CString object named string to the list box. If the list box style includes LBS_SORT, the string is positioned according to its lexical value; otherwise, it is added to the end of the list. InsertString adds an item to the list box at a location specified by a 0-based index. The statement
m_wndListBox.InsertString (3, string); |
inserts string into the list box and makes it the fourth item. LBS_SORT has no effect on strings added with InsertString.
Both AddString and InsertString return a 0-based index specifying the string's position in the list box. If either function fails, it returns LB_ERRSPACE to indicate that the list box is full or LB_ERR to indicate that the insertion failed for other reasons. You shouldn't see the LB_ERRSPACE return value very often in 32-bit Windows because the capacity of a list box is limited only by available memory. CListBox::GetCount returns the number of items in a list box.
CListBox::DeleteString removes an item from a list box. It takes a single parameter: the index of the item to be removed. It returns the number of items remaining in the list box. To remove all items from a list box at once, use CListBox::ResetContent.
If desired, you can use CListBox::SetItemDataPtr or CListBox::SetItemData to associate a 32-bit pointer or a DWORD value with an item in a list box. A pointer or DWORD associated with an item can be retrieved with CListBox::GetItemDataPtr or CListBox::GetItemData. One use for this feature is to associate extra data with the items in a list box. For example, you could associate a data structure containing an address and a phone number with a list box item holding a person's name. Because GetItemDataPtr returns a pointer to a void data type, you'll need to cast the pointer that it returns.
Another technique programmers use to associate extra data—particularly text-based data—with list box items is to create an LBS_USETABSTOPS-style list box, set the first tab stop to a position beyond the list box's right border, and append a string consisting of a tab character followed by the extra data to the list box item. The text to the right of the tab character will be invisible, but CListBox::GetText will return the full text of the list box item—additional text included.
Finding and Retrieving Items
The CListBox class also includes member functions for getting and setting the current selection and for finding and retrieving items. CListBox::GetCurSel returns the 0-based index of the item that is currently selected. A return value equal to LB_ERR means that nothing is selected. GetCurSel is often called following a notification signifying that the selection changed or an item was double-clicked. A program can set the current selection with the SetCurSel function. Passing SetCurSel the value -1 deselects all items, causing the bar highlighting the current selection to disappear from the list box. To find out whether a particular item is selected, use CListBox::GetSel.
SetCurSel identifies an item by its index, but items can also be selected by content. CListBox::SelectString searches a single-selection list box for an item that begins with a specified text string and selects the item if a match is found. The statement
m_wndListBox.SelectString (-1, _T ("Times")); |
starts the search with the first item in the list box and highlights the first item that begins with "Times"—for example, "Times New Roman" or "Times Roman." The search is not case-sensitive. The first parameter to SelectString specifies the index of the item before the one at which the search begins; -1 instructs the list box to start with item 0. If the search is begun anywhere else, the search will wrap around to the first item if necessary so that all list box items are searched.
To search a list box for a particular item without changing the selection, use CListBox::FindString or CListBox::FindStringExact. FindString performs a string search on a list box's contents and returns the index of the first item whose text matches or begins with a specified string. A return value equal to LB_ERR means that no match was found. FindStringExact does the same but reports a match only if the item text matches the search text exactly. Once you have an item's index in hand, you can retrieve the text of the item with CListBox::GetText. The following statements query the list box for the currently selected item and copy the text of that item to a CString named string:
CString string; int nIndex = m_wndListBox.GetCurSel (); if (nIndex != LB_ERR) m_wndListBox.GetText (nIndex, string); |
An alternative form of GetText accepts a pointer to a character array rather than a CString reference. You can use CListBox::GetTextLen to determine how large the array should be before calling the array version of GetText.
Selections in multiple-selection list boxes are handled differently than selections in single-selection list boxes. In particular, the GetCurSel, SetCurSel, and SelectString functions don't work with multiple-selection list boxes. Instead, items are selected (and deselected) with the SetSel and SelItemRange functions. The following statements select items 0, 5, 6, 7, 8, and 9 and deselect item 3:
m_wndListBox.SetSel (0); m_wndListBox.SelItemRange (TRUE, 5, 9); m_wndListBox.SetSel (3, FALSE); |
CListBox also provides the GetSelCount function for getting a count of selected items and the GetSelItems function for retrieving the indexes of all selected items. In a multiple-selection list box, the dotted rectangle representing the item with the focus can be moved without changing the current selection. The focus rectangle can be moved and queried with SetCaretIndex and GetCaretIndex. Most other list box functions, including GetText, GetTextLength, FindString, and FindStringExact, work the same for multiple-selection list boxes as they do for the single-selection variety.
List Box Notifications
A list box sends notifications to its parent window via WM_COMMAND messages. In an MFC application, list box notifications are mapped to class member functions with ON_LBN message-map entries. The table below lists the six notification types and the corresponding ON_LBN macros. LBN_DBLCLK, LBN_SELCHANGE, and LBN_SELCANCEL notifications are sent only if the list box was created with the style LBS_NOTIFY or LBS_STANDARD. The others are sent regardless of list box style.
List Box Notifications
Notification | Sent When | Message-Map Macro | LBS_NOTIFY Required? |
---|---|---|---|
LBN_SETFOCUS | The list box gains the input focus. | ON_LBN_SETFOCUS | No |
LBN_KILLFOCUS | The list box loses the input focus. | ON_LBN_KILLFOCUS | No |
LBN_ERRSPACE | An operation failed because of insufficient memory. | ON_LBN_ERRSPACE | No |
LBN_DBLCLK | An item is double-clicked. | ON_LBN_DBLCLK | Yes |
LBN_SELCHANGE | The selection changes. | ON_LBN_SELCHANGE | Yes |
LBN_SELCANCEL | The selection is canceled. | ON_LBN_SELCANCEL | Yes |
The two list box notifications that programmers rely on most are LBN_DBLCLK and LBN_SELCHANGE. LBN_DBLCLK is sent when a list box item is double-clicked. To determine the index of the item that was double-clicked in a single-selection list box, use CListBox::GetCurSel. The following code fragment displays the item in a message box:
// In CMainWindow's message map ON_LBN_DBLCLK (IDC_LISTBOX, OnItemDoubleClicked) |
For a multiple-selection list box, use GetCaretIndex instead of GetCurSel to determine which item was double-clicked.
A list box sends an LBN_SELCHANGE notification when the user changes the selection, but not when the selection is changed programmatically. A single-selection list box sends an LBN_SELCHANGE notification when the selection moves because of a mouse click or keystroke. A multiple-selection list box sends an LBN_SELCHANGE notification when an item is clicked, when an item's selection state is toggled with the spacebar, and when the focus rectangle is moved.
The CStatic Class
CStatic, which represents static controls created from the "STATIC" WNDCLASS, is the simplest of the MFC control classes. At least it used to be: Windows 95 added so many new features to static controls that CStatic now rivals CButton and some of the other control classes for complexity.
Static controls come in three flavors: text, rectangles, and images. Static text controls are often used to label other controls. The following statement creates a static text control that displays the string "Name":
m_wndStatic.Create (_T ("Name"), WS_CHILD ¦ WS_VISIBLE ¦ SS_LEFT, rect, this, IDC_STATIC); |
SS_LEFT creates a static text control whose text is left-aligned. If the control text is too long to fit on one line, it wraps around to the next one. To prevent wrapping, use SS_LEFTNOWORDWRAP instead of SS_LEFT. Text can be centered horizontally or right-aligned in a static control by substituting SS_CENTER or SS_RIGHT for SS_LEFT or SS_LEFTNOWORDWRAP. Another alternative is the little-used SS_SIMPLE style, which is similar to SS_LEFT but creates a control whose text can't be altered with CWnd::SetWindowText.
By default, the text assigned to a static text control is aligned along the upper edge of the control rectangle. To center text vertically in the control rectangle, OR an SS_CENTERIMAGE flag into the control style. You can also draw a sunken border around a static control by including the style SS_SUNKEN.
A second use for static controls is to draw rectangles. The control style specifies the type of rectangle that is drawn. Here are the styles you can choose from:
Style | Description |
---|---|
SS_BLACKFRAME | Hollow rectangle painted in the system color COLOR_WINDOWFRAME (default = black) |
SS_BLACKRECT | Solid rectangle painted in the system color COLOR_WINDOWFRAME (default = black) |
SS_ETCHEDFRAME | Hollow rectangle with etched borders |
SS_ETCHEDHORZ | Hollow rectangle with etched top and bottom borders |
SS_ETCHEDVERT | Hollow rectangle with etched left and right borders |
SS_GRAYFRAME | Hollow rectangle painted in the system color COLOR_BACKGROUND (default = gray) |
SS_GRAYRECT | Solid rectangle painted in the system color COLOR_BACKGROUND (default = gray) |
SS_WHITEFRAME | Hollow rectangle painted in the system color COLOR_WINDOW (default = white) |
SS_WHITERECT | Solid rectangle painted in the system color COLOR_WINDOW (default = white) |
The statement
m_wndStatic.Create (_T (""), WS_CHILD ¦ WS_VISIBLE ¦ SS_ETCHEDFRAME, rect, this, IDC_STATIC); |
creates a static control that resembles a group box. For best results, you should draw etched rectangles on surfaces whose color is the same as the default dialog box color (the system color COLOR_3DFACE). A static rectangle control doesn't display text, even if you specify a nonnull text string in the call to Create.
A third use for static controls is to display images formed from bitmaps, icons, cursors, or GDI metafiles. A static image control uses one of the following styles:
Style | Description |
---|---|
SS_BITMAP | A static control that displays a bitmap |
SS_ENHMETAFILE | A static control that displays a metafile |
SS_ICON | A static control that displays an icon or a cursor |
After creating an image control, you associate a bitmap, metafile, icon, or cursor with it by calling its SetBitmap, SetEnhMetaFile, SetIcon, or SetCursor function. The statements
m_wndStatic.Create (_T (""), WS_CHILD ¦ WS_VISIBLE ¦ SS_ICON, rect, this, IDC_STATIC); m_wndStatic.SetIcon (hIcon); |
create a static control that displays an icon and assign it the icon whose handle is hIcon. By default, the icon image is positioned in the upper left corner of the control, and if the image is larger than the control rectangle, the rectangle is automatically expanded so the image won't be clipped. To center the image in the control rectangle, OR SS_CENTERIMAGE into the control style. SS_CENTERIMAGE prevents the system from automatically sizing the control rectangle if it's too small to show the entire image, so if you use SS_CENTERIMAGE, be sure that the control rectangle is large enough to display the image. Sizing isn't an issue with SS_ENHMETAFILE-style controls because metafile images scale to match the control size. For a neat special effect, place a sunken border around an image control by ORing SS_SUNKEN into the control style.
By default, a static control sends no notifications to its parent. But a static control created with the SS_NOTIFY style sends the four types of notifications listed in the following table.
Static Control Notifications
Notification | Sent When | Message-Map Macro |
---|---|---|
STN_CLICKED | The control is clicked. | ON_STN_CLICKED |
STN_DBLCLK | The control is double-clicked. | ON_STN_DBLCLK |
STN_DISABLE | The control is disabled. | ON_STN_DISABLE |
STN_ENABLE | The control is enabled. | ON_STN_ENABLE |
The STN_CLICKED and STN_DBLCLK notifications allow you to create static controls that respond to mouse clicks. The statements
// In CMainWindow's message map ON_STN_CLICKED (IDC_STATIC, OnClicked) |
create a static control that displays "Click me" in the center of a sunken rectangle and disappears from the screen when clicked. If a static control lacks the SS_NOTIFY style, mouse messages go through to the underlying window because the control's window procedure returns HTTRANSPARENT in response to WM_NCHITTEST messages.
The FontView Application
Let's put what we've learned so far about buttons, list boxes, and static controls to use in an application. The FontView program shown in Figure 7-3 lists the names of all the fonts installed on the host PC in a list box. When a font name is selected, a sample is drawn in the group box at the bottom of the window. The sample text is really a static control, so all FontView has to do to display a font sample is call the control's SetFont function. If the check box labeled Show TrueType Fonts Only is checked, non-TrueType fonts are excluded from the list. In addition to showing how push button, check box, list box, group box, and static controls are used, FontView also demonstrates a very important MFC programming technique—the use of C++ member functions as callback functions. The term callback function might not mean much to you at the moment, but you'll learn all about it shortly.
Figure 7-3. The FontView window.
FontView's source code appears in Figure 7-4. The controls are created one by one in CMainWindow::OnCreate. All but one—the static control that displays the font sample—is assigned an 8-point MS Sans Serif font. Rather than use raw pixel counts to size and position the controls, FontView uses distances based on the width and height of 8-point MS Sans Serif characters to achieve independence from the physical resolution of the display device. The character height and width are measured by selecting the font into a device context and calling CDC::GetTextMetrics with a pointer to a TEXTMETRIC structure:
CFont* pOldFont = dc.SelectObject (&m_fontMain); TEXTMETRIC tm; dc.GetTextMetrics (&tm); m_cxChar = tm.tmAveCharWidth; m_cyChar = tm.tmHeight + tm.tmExternalLeading; |
On return, the structure's tmAveCharWidth field holds the average character width. (Actual character width can vary from character to character in a proportionally spaced font.) Summing the tmHeight and tmExternalLeading fields yields the height of one line of text, including interline spacing.
Figure 7-4. The FontView application.
FontView.h
class CMyApp : public CWinApp { public: virtual BOOL InitInstance (); }; class CMainWindow : public CWnd { protected: int m_cxChar; int m_cyChar; CFont m_fontMain; CFont m_fontSample; CStatic m_wndLBTitle; CListBox m_wndListBox; CButton m_wndCheckBox; CButton m_wndGroupBox; CStatic m_wndSampleText; CButton m_wndPushButton; void FillListBox (); public: CMainWindow (); static int CALLBACK EnumFontFamProc (ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam); protected: virtual void PostNcDestroy (); afx_msg int OnCreate (LPCREATESTRUCT lpcs); afx_msg void OnPushButtonClicked (); afx_msg void OnCheckBoxClicked (); afx_msg void OnSelChange (); DECLARE_MESSAGE_MAP () }; |
FontView.cpp
#include <afxwin.h> #include "FontView.h" #define IDC_PRINT 100 #define IDC_CHECKBOX 101 #define IDC_LISTBOX 102 #define IDC_SAMPLE 103 CMyApp myApp; ///////////////////////////////////////////////////////////////////////// // CMyApp member functions BOOL CMyApp::InitInstance () { m_pMainWnd = new CMainWindow; m_pMainWnd->ShowWindow (m_nCmdShow); m_pMainWnd->UpdateWindow (); return TRUE; } ///////////////////////////////////////////////////////////////////////// // CMainWindow message map and member functions BEGIN_MESSAGE_MAP (CMainWindow, CWnd) ON_WM_CREATE () ON_BN_CLICKED (IDC_PRINT, OnPushButtonClicked) ON_BN_CLICKED (IDC_CHECKBOX, OnCheckBoxClicked) ON_LBN_SELCHANGE (IDC_LISTBOX, OnSelChange) END_MESSAGE_MAP () CMainWindow::CMainWindow () { CString strWndClass = AfxRegisterWndClass ( 0, myApp.LoadStandardCursor (IDC_ARROW), (HBRUSH) (COLOR_3DFACE + 1), myApp.LoadStandardIcon (IDI_WINLOGO) ); CreateEx (0, strWndClass, _T ("FontView"), WS_OVERLAPPED ¦ WS_SYSMENU ¦ WS_CAPTION ¦ WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL); CRect rect (0, 0, m_cxChar * 68, m_cyChar * 26); CalcWindowRect (&rect); SetWindowPos (NULL, 0, 0, rect.Width (), rect.Height (), SWP_NOZORDER ¦ SWP_NOMOVE ¦ SWP_NOREDRAW); } int CMainWindow::OnCreate (LPCREATESTRUCT lpcs) { if (CWnd::OnCreate (lpcs) == -1) return -1; // // Create an 8-point MS Sans Serif font to use in the controls. // m_fontMain.CreatePointFont (80, _T ("MS Sans Serif")); // // Compute the average width and height of a character in the font. // CClientDC dc (this); CFont* pOldFont = dc.SelectObject (&m_fontMain); TEXTMETRIC tm; dc.GetTextMetrics (&tm); m_cxChar = tm.tmAveCharWidth; m_cyChar = tm.tmHeight + tm.tmExternalLeading; dc.SelectObject (pOldFont); // // Create the controls that will appear in the FontView window. // CRect rect (m_cxChar * 2, m_cyChar, m_cxChar * 48, m_cyChar * 2); m_wndLBTitle.Create (_T ("Typefaces"), WS_CHILD ¦ WS_VISIBLE ¦ SS_LEFT, rect, this); rect.SetRect (m_cxChar * 2, m_cyChar * 2, m_cxChar * 48, m_cyChar * 18); m_wndListBox.CreateEx (WS_EX_CLIENTEDGE, _T ("listbox"), NULL, WS_CHILD ¦ WS_VISIBLE ¦ LBS_STANDARD, rect, this, IDC_LISTBOX); rect.SetRect (m_cxChar * 2, m_cyChar * 19, m_cxChar * 48, m_cyChar * 20); m_wndCheckBox.Create (_T ("Show TrueType fonts only"), WS_CHILD ¦ WS_VISIBLE ¦ BS_AUTOCHECKBOX, rect, this, IDC_CHECKBOX); rect.SetRect (m_cxChar * 2, m_cyChar * 21, m_cxChar * 66, m_cyChar * 25); m_wndGroupBox.Create (_T ("Sample"), WS_CHILD ¦ WS_VISIBLE ¦ BS_GROUPBOX, rect, this, (UINT) -1); rect.SetRect (m_cxChar * 4, m_cyChar * 22, m_cxChar * 64, (m_cyChar * 99) / 4); m_wndSampleText.Create (_T (""), WS_CHILD ¦ WS_VISIBLE ¦ SS_CENTER, rect, this, IDC_SAMPLE); rect.SetRect (m_cxChar * 50, m_cyChar * 2, m_cxChar * 66, m_cyChar * 4); m_wndPushButton.Create (_T ("Print Sample"), WS_CHILD ¦ WS_VISIBLE ¦ WS_DISABLED ¦ BS_PUSHBUTTON, rect, this, IDC_PRINT); // // Set each control's font to 8-point MS Sans Serif. // m_wndLBTitle.SetFont (&m_fontMain, FALSE); m_wndListBox.SetFont (&m_fontMain, FALSE); m_wndCheckBox.SetFont (&m_fontMain, FALSE); m_wndGroupBox.SetFont (&m_fontMain, FALSE); m_wndPushButton.SetFont (&m_fontMain, FALSE); // // Fill the list box with typeface names and return. // FillListBox (); return 0; } void CMainWindow::PostNcDestroy () { delete this; } void CMainWindow::OnPushButtonClicked () { MessageBox (_T ("This feature is currently unimplemented. Sorry!"), _T ("Error"), MB_ICONINFORMATION ¦ MB_OK); } void CMainWindow::OnCheckBoxClicked () { FillListBox (); OnSelChange (); } void CMainWindow::OnSelChange () { int nIndex = m_wndListBox.GetCurSel (); if (nIndex == LB_ERR) { m_wndPushButton.EnableWindow (FALSE); m_wndSampleText.SetWindowText (_T ("")); } else { m_wndPushButton.EnableWindow (TRUE); if ((HFONT) m_fontSample != NULL) m_fontSample.DeleteObject (); CString strFaceName; m_wndListBox.GetText (nIndex, strFaceName); m_fontSample.CreateFont (-m_cyChar * 2, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH ¦ FF_DONTCARE, strFaceName); m_wndSampleText.SetFont (&m_fontSample); m_wndSampleText.SetWindowText (_T ("AaBbCcDdEeFfGg")); } } void CMainWindow::FillListBox () { m_wndListBox.ResetContent (); CClientDC dc (this); ::EnumFontFamilies ((HDC) dc, NULL, (FONTENUMPROC) EnumFontFamProc, (LPARAM) this); } int CALLBACK CMainWindow::EnumFontFamProc (ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam) { CMainWindow* pWnd = (CMainWindow*) lParam; if ((pWnd->m_wndCheckBox.GetCheck () == BST_UNCHECKED) || (nFontType & TRUETYPE_FONTTYPE)) pWnd->m_wndListBox.AddString (lpelf->elfLogFont.lfFaceName); return 1; } |
CMainWindow processes three types of control notifications: BN_CLICKED notifications from the push button, BN_CLICKED notifications from the check box, and LBN_SELCHANGE notifications from the list box. The corresponding message-map entries look like this:
ON_BN_CLICKED (IDC_PRINT, OnPushButtonClicked) ON_BN_CLICKED (IDC_CHECKBOX, OnCheckBoxClicked) ON_LBN_SELCHANGE (IDC_LISTBOX, OnSelChange) |
OnPushButtonClicked is activated when the Print Sample button is clicked. Because printing is a complex undertaking in a Windows application, OnPushButtonClicked does nothing more than display a message box. OnCheckBoxClicked handles BN_CLICKED notifications from the check box. Since the check box style includes a BS_AUTOCHECKBOX flag, the check mark toggles on and off automatically in response to button clicks. OnCheckBoxClicked's job is to refresh the contents of the list box each time the check mark is toggled. To do that, it calls CMainWindow::FillListBox to reinitialize the list box and then calls CMainWindow::OnSelChange to update the sample text.
OnSelChange is also called whenever the list box selection changes. It calls GetCurSel to get the index of the currently selected item. If GetCurSel returns LB_ERR, indicating that nothing is selected, OnSelChange disables the push button and erases the sample text. Otherwise, it enables the button, retrieves the text of the selected item with CListBox::GetText, and creates a font whose typeface name equals the string returned by GetText. It then assigns the font to the static control and sets the control text to "AaBbCcDdEeFfGg."
Font Enumerations and Callback Functions
The job of filling the list box with font names falls to CMainWindow::FillListBox. FillListBox is called by OnCreate to initialize the list box when the program is started. It is also called by OnCheckBoxClicked to reinitialize the list box when the Show TrueType Fonts Only check box is clicked. FillListBox first clears the list box by calling CListBox::ResetContent. It then enumerates all the fonts installed in the system and adds the corresponding typeface names to the list box.
FillListBox begins the enumeration process by constructing a device context object named dc, using the CDC class's HDC operator to extract a device context handle, and passing that handle to the ::EnumFontFamilies function:
CClientDC dc (this); ::EnumFontFamilies ((HDC) dc, NULL, (FONTENUMPROC) EnumFontFamProc, (LPARAM) this); |
The NULL second parameter tells ::EnumFontFamilies to enumerate all installed fonts. The next parameter is the address of a callback function. A callback function is a function in your application that Windows calls back with information you requested. For each font that ::EnumFontFamilies enumerates, Windows calls your callback function one time. An ::EnumFontFamilies callback function must be prototyped like this:
int CALLBACK EnumFontFamProc (ENUMLOGFONT* lpelf, NEWTEXTMETRIC* lpntm, int nFontType, LPARAM lParam) |
lpelf is a pointer to an ENUMLOGFONT structure, which contains a wealth of information about the font, including its typeface name. lpntm is a pointer to a structure of type NEWTEXTMETRIC, which contains font metrics—height, average character width, and so on. nFontType specifies the font type. TrueType fonts are identified by logically ANDing nFontType with the value TRUETYPE_FONTTYPE. If the result is nonzero, the font is a TrueType font. The fourth and final parameter, lParam, is an optional 32-bit LPARAM value passed to ::EnumFontFamilies. FillListBox passes the this pointer referring to CMainWindow, for reasons I'll explain in a moment.
FontView's callback function is a member of CMainWindow. It's actually the callback function, not FillListBox, that adds the typeface names to the list box. Each time CMainWindow::EnumFontFamProc is called, it casts the lParam value passed to it from FillListBox into a CMainWindow pointer:
CMainWindow* pWnd = (CMainWindow*) lParam; |
It then uses the pointer to add the typeface name to the list box, but only if the Show TrueType Fonts Only check box is unchecked or the font is a TrueType font:
if ((pWnd->m_wndCheckBox.GetCheck () == BST_UNCHECKED) ¦¦ (nFontType & TRUETYPE_FONTTYPE)) pWnd->m_wndListBox.AddString (lpelf->elfLogFont.lfFaceName); return 1; |
The nonzero return value tells Windows to continue the enumeration process. (The callback function can halt the process at any time by returning 0, a handy option to have if you've allocated a fixed amount of memory to store font information and the memory fills up.) After Windows has called EnumFontFamProc for the last time, the call that FillListBox placed to ::EnumFontFamilies returns and the enumeration process is complete.
Why does FillListBox pass a this pointer to the callback function, and why does EnumFontFamProc cast the pointer to a CMainWindow pointer when it, too, is a member of CMainWindow? Look closely at the declaration for CMainWindow in FontView.h, and you'll see that EnumFontFamProc is a static member function.A static class member function doesn't receive a this pointer, so it can't access nonstatic members of its own class. To call m_wndCheckBox's GetCheck function and m_wndListBox's AddString, EnumFontFamProc needs pointers to m_wndCheckBox and m_wndListBox or a pointer to the CMainWindow object to which those objects belong. By casting the lParam value passed to FillListBox to a CMainWindow pointer, EnumFontFamProc is able to access nonstatic members of the CMainWindow class just as if it were a nonstatic member function.
EnumFontFamProc is static because callbacks require special handling in C++ applications. Windows rigidly defines a callback function's interface—the parameters passed to it through its argument list. When a member function of a C++ class is declared, the compiler silently tacks on an extra argument to hold the this pointer. Unfortunately, the added parameter means that the callback function's argument list doesn't match the argument list Windows expects, and all sorts of bad things can happen as a result, including invalid memory access errors, the nemeses of all Windows programmers. There are several solutions to this problem, but declaring the callback to be a static member function is among the simplest and most direct. In C++, a static member function isn't passed a this pointer, so its argument list is unaltered.
Callback functions are common in Windows, so the technique demonstrated here is useful for more than just enumerating fonts. Many Windows API functions that rely on callbacks support an application-defined lParam value, which is perfect for passing this pointers to statically declared callback functions. Should you use an enumeration function that doesn't support an application-defined lParam, you'll have to resort to other means to make a pointer available. One alternative is to make the this pointer visible to the callback function by copying it to a global variable.
The CEdit Class
MFC's CEdit class encapsulates the functionality of edit controls. Edit controls are used for text entry and editing and come in two varieties: single-line and multiline. Single-line edit controls are perfect for soliciting one-line text strings such as names, passwords, and product IDs. (See Figure 7-5.) To see a multiline edit control in action, start the Notepad applet that comes with Windows. The client area of the Notepad window is a multiline edit control.
Figure 7-5. A dialog box with two single-line edit controls.
An edit control is limited to about 60 KB of text. That's not much of a restriction for single-line edit controls, but for a multiline edit control it can be constraining. If you need to handle large amounts of text, use the rich edit control instead—an enhanced version of the standard edit control that is part of the common controls library. Though designed to handle richly formatted text of the type seen in word processors, rich edit controls are quite capable of handling ordinary text, too. The Windows WordPad applet uses a rich edit control for text entry and editing. You'll use a rich edit control to build a WordPad-like application of your own in Chapter 12.
Creating an Edit Control
If m_wndEdit is a CEdit object, the statement
m_wndEdit.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER ¦ ES_AUTOHSCROLL, rect, this, IDC_EDIT); |
creates a single-line edit control that automatically scrolls horizontally when the caret moves beyond the control's border. Including ES_MULTILINE in the window style creates a multiline edit control instead:
m_wndEdit.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER ¦ WS_HSCROLL ¦ WS_VSCROLL ¦ ES_MULTILINE, rect, this, IDC_EDIT); |
WS_HSCROLL and WS_VSCROLL add horizontal and vertical scroll bars to the control. You can use CEdit::SetRect or CEdit::SetRectNP to define the control's editable area independent of the control's borders. One use for these functions is to define a page size that remains constant even if the control is resized. You can also use CEdit::SetMargins to specify left and right margin widths in pixels. The default margin widths are 0. The window styles listed in the table below are specific to edit controls.
When it is first created, an edit control will accept only about 30,000 characters. You can raise or lower the limit with CEdit::LimitText or the Win32-specific CEdit::SetLimitText. The following statement sets the maximum number of characters that an edit control will accept to 32:
m_wndEdit.SetLimitText (32); |
When used with a multiline edit control, SetLimitText limits the total amount of text entered into the control, not the length of each line. There is no built-in way to limit the number of characters per line in a multiline edit control, but there are ways you can do it manually. One approach is to use SetFont to switch the edit control font to a fixed-pitch font and CEdit::SetRect to specify a formatting rectangle whose width is slightly greater than the width of a character times the desired number of characters per line.
Edit Control Styles
Style | Description |
---|---|
ES_LEFT | Left-aligns text in the control. |
ES_CENTER | Centers text in the control. |
ES_RIGHT | Right-aligns text in the control. |
ES_AUTOHSCROLL | Permits the edit control to scroll horizontally without a horizontal scroll bar. To add a horizontal scroll bar, include the style WS_HSCROLL. |
ES_AUTOVSCROLL | Permits the edit control to scroll vertically without a vertical scroll bar. To add a vertical scroll bar, include the style WS_VSCROLL. |
ES_MULTILINE | Creates a multiline edit control. |
ES_LOWERCASE | Displays all characters in lowercase. |
ES_UPPERCASE | Displays all characters in uppercase. |
ES_PASSWORD | Displays asterisks instead of typed characters. |
ES_READONLY | Creates an edit control whose text can't be edited. |
ES_NOHIDESEL | Prevents the edit control from hiding the selection when the control loses the input focus. |
ES_OEMCONVERT | Performs an ANSI-to-OEM-to-ANSI conversion on all characters typed into the control so that the application won't get unexpected results if it performs an ANSI-to-OEM conversion of its own. Obsolete. |
ES_WANTRETURN | Programs the Enter key to insert line breaks instead of invoking the default push button for multiline edit controls used in dialog boxes. |
Another function sometimes used to initialize an edit control is CEdit::SetTabStops, which sets the spacing between tab stops. Default tab stops are set about 8 character widths apart. You can space the tab stops however you like and can even vary the spacing between stops. Like CListBox::SetTabStops, CEdit::SetTabStops measures distances in dialog units.
Inserting and Retrieving Text
Text is inserted into an edit control with SetWindowText and retrieved with GetWindowText. CEdit inherits both functions from its base class, CWnd. The statement
m_wndEdit.SetWindowText (_T ("Hello, MFC")); |
inserts the text string "Hello, MFC" into the edit control m_wndEdit, and
m_wndEdit.GetWindowText (string); |
retrieves the text into a CString object named string. GetWindowText and SetWindowText work with both single-line and multiline edit controls. Text inserted with SetWindowText replaces existing text, and GetWindowText returns all the text in the edit control, even if the text spans multiple lines. To erase all the text in an edit control, call SetWindowText with a null string:
m_wndEdit.SetWindowText (_T ("")); |
You can insert text into an edit control without erasing what's already there with CEdit::ReplaceSel. If one or more characters are selected when ReplaceSel is called, the inserted text replaces the selected text; otherwise, the new text is inserted at the current caret position.
A multiline edit control inserts line breaks automatically. If you'd like to know where the line breaks fall in text retrieved from a multiline edit control, use CEdit::FmtLines to enable soft line breaks before calling GetWindowText:
m_wndEdit.FmtLines (TRUE); |
With soft line breaks enabled, each line is delimited with two carriage returns (13) followed by a line feed character (10). To disable soft line breaks, call FmtLines with a FALSE parameter:
m_wndEdit.FmtLines (FALSE); |
Now line breaks won't be denoted in any special way. Hard returns—line breaks inserted manually when the user presses the Enter key—are signified by single carriage return/line feed pairs regardless of the FmtLines setting. FmtLines doesn't affect the appearance of the text in a multiline edit control. It affects only the way in which the control stores text internally and the format of text retrieved with GetWindowText.
To read just one line of text from a multiline edit control, use CEdit::GetLine. GetLine copies the contents of a line to a buffer whose address you provide. The line is identified with a 0-based index. The statement
m_wndEdit.GetLine (0, pBuffer, nBufferSize); |
copies the first line of text in a multiline edit control to the buffer pointed to by pBuffer. The third parameter is the buffer size, in bytes (not characters). GetLine returns the number of bytes copied to the buffer. You can determine how much buffer space you need before retrieving a line with CEdit::LineLength. And you can find out how many lines of text a multiline edit control contains by calling CEdit::GetLineCount. Note that GetLineCount never returns 0; the return value is 1 even if no text has been entered.
Clear, Cut, Copy, Paste, and Undo
CEdit provides easy-to-use member functions that perform the programmatic equivalents of the Clear, Cut, Copy, Paste, and Undo items in the Edit menu. The statement
m_wndEdit.Clear (); |
removes the selected text without affecting what's on the clipboard. The statement
m_wndEdit.Cut (); |
removes the selected text and copies it to the clipboard. And the statement
m_wndEdit.Copy (); |
copies the selected text to the clipboard without altering the contents of the edit control.
You can query an edit control for the current selection by calling CEdit::GetSel, which returns a DWORD value with two packed 16-bit integers specifying the indexes of the beginning and ending characters in the selection. An alternate form of GetSel copies the indexes to a pair of integers whose addresses are passed by reference. If the indexes are equal, no text is currently selected. The following IsTextSelected function, which you might add to an edit control class derived from CEdit, returns a nonzero value if a selection exists and 0 if one doesn't exist:
BOOL CMyEdit::IsTextSelected () { int nStart, nEnd; GetSel (nStart, nEnd); return (nStart != nEnd); } |
CEdit::Cut and CEdit::Copy do nothing if no text is selected.
Text can be selected programmatically with CEdit::SetSel. The statement
m_wndEdit.SetSel (100, 150); |
selects 50 characters beginning with the 101st (the character whose 0-based index is 100) and scrolls the selection into view if it isn't visible already. To prevent scrolling, include a third parameter and set it equal to TRUE.
When programmatically selecting text in a multiline edit control, you often need to convert a line number and possibly an offset within that line into an index that can be passed to SetSel. CEdit::LineIndex accepts a 0-based line number and returns the index of the first character in that line. The next example uses LineIndex to determine the index of the first character in the eighth line of a multiline edit control, LineLength to retrieve the line's length, and SetSel to select everything on that line:
int nStart = m_wndEdit.LineIndex (7); int nLength = m_wndEdit.LineLength (nStart); m_wndEdit.SetSel (nStart, nStart + nLength); |
CEdit also provides a function named LineFromChar for computing a line number from a character index.
CEdit::Paste pastes text into an edit control. The following statement pastes the text that currently resides in the Windows clipboard into an edit control named m_wndEdit:
m_wndEdit.Paste (); |
If the clipboard contains no text, CEdit::Paste does nothing. If no text is selected when Paste is called, the clipboard text is inserted at the current caret position. If a selection exists, the text retrieved from the clipboard replaces the text selected in the control. You can determine ahead of time whether the clipboard contains text (and therefore whether the Paste function will actually do anything) by calling ::IsClipboardFormatAvailable. The statement
BOOL bCanPaste = ::IsClipboardFormatAvailable (CF_TEXT); |
sets bCanPaste to nonzero if text is available from the clipboard, and 0 if it isn't.
Edit controls also feature a built-in undo capability that "rolls back" the previous editing operation. The statement
m_wndEdit.Undo (); |
undoes the last operation, provided that the operation can be undone. You can determine ahead of time whether calling Undo will accomplish anything with CEdit::CanUndo. A related function, CEdit::EmptyUndoBuffer, manually resets the undo flag so that subsequent calls to Undo will do nothing (and calls to CanUndo will return FALSE) until another editing operation is performed.
Edit Control Notifications
Edit controls send notifications to their parents to report various input events. In MFC applications, these notifications are mapped to handling functions with ON_EN message map macros. Edit control notifications and the corresponding message map macros are summarized in the table below.
A common use for EN_CHANGE notifications is to dynamically update other controls as text is entered into an edit control. The following code updates a push button (m_wndPushButton) as text is entered into an edit control (m_wndEdit, ID=IDC_EDIT) so that the push button is enabled if the edit control contains at least one character and disabled if it doesn't:
// In CMainWindow's message map ON_EN_CHANGE (IDC_EDIT, OnUpdatePushButton) |
Edit Control Notifications
Notification | Sent When | Message-Map Macro |
---|---|---|
EN_UPDATE | The control's text is about to change. | ON_EN_UPDATE |
EN_CHANGE | The control's text has changed. | ON_EN_CHANGE |
EN_KILLFOCUS | The edit control loses the input focus. | ON_EN_KILLFOCUS |
EN_SETFOCUS | The edit control receives the input focus. | ON_EN_SETFOCUS |
EN_HSCROLL | The edit control is scrolled horizontally using a scroll bar. | ON_EN_HSCROLL |
EN_VSCROLL | The edit control is scrolled vertically using a scroll bar. | ON_EN_VSCROLL |
EN_MAXTEXT | A character can't be entered because the edit control already contains the number of characters specified with CEdit::LimitText or CEdit::SetLimitText. This notification is also sent if a character can't be entered because the caret is at the right or the bottom edge of the control's formatting rectangle and the control doesn't support scrolling. | ON_EN_MAXTEXT |
EN_ERRSPACE | An operation fails because of insufficient memory. | ON_EN_ERRSPACE |
Providing interactive feedback of this nature is generally considered good user interface design. Most users would rather see a button remain disabled until all of the required information is entered than click a button and receive an error message.
Presto! Instant Notepad
The MyPad application, portions of whose source code are reproduced in Figure 7-6, uses a multiline edit control to create a near clone of the Windows Notepad applet. As you can see from the source code, the edit control does the bulk of the work. CEdit functions such as Undo and Cut allow you to implement commands in the Edit menu with just one line of code.
MyPad is a view-based application that I began by running the MFC AppWizard but unchecking the Document/View Architecture Support box in Step 1. To avoid unnecessary code, I unchecked the ActiveX Controls box in AppWizard's Step 3 dialog, too. After running AppWizard, I added a New command to the File menu and a Delete command to the Edit menu using the Visual C++ resource editor. I also used the resource editor to add an accelerator (Ctrl-N) for the New command. I then used ClassWizard to add command handlers, update handlers, and message handlers.
The view's WM_CREATE message handler creates the edit control by calling Create on the CEdit data member named m_wndEdit. OnCreate sets the control's width and height to 0, but OnSize resizes the control to fill the view's client area whenever the view receives a WM_SIZE message. The first WM_SIZE message arrives before the view becomes visible on the screen; subsequent WM_SIZE messages arrive anytime the MyPad window (and consequently, the view) is resized. A one-line WM_SETFOCUS handler in the view class shifts the input focus to the edit control whenever the view receives the input focus.
Figure 7-6. The MyPad application.
MainFrm.h
// MainFrm.h : interface of the CMainFrame class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_MAINFRM_H__0FA1D288_8471_11D2_8E53_006008A82731__INCLUDED_) #define AFX_MAINFRM_H__0FA1D288_8471_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "ChildView.h" class CMainFrame : public CFrameWnd { public: CMainFrame(); protected: DECLARE_DYNAMIC(CMainFrame) // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CMainFrame(); #ifdef _DEBUG virtual void AssertValid() const; virtual void Dump(CDumpContext& dc) const; #endif CChildView m_wndView; // Generated message map functions protected: //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_MAINFRM_H__0FA1D288_8471_11D2_8E53_006008A82731__INCLUDED_) |
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class // #include "stdafx.h" #include "MyPad.h" #include "MainFrm.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CMainFrame IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd) BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CMainFrame construction/destruction CMainFrame::CMainFrame() { } CMainFrame::~CMainFrame() { } BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; cs.dwExStyle &= ~WS_EX_CLIENTEDGE; cs.lpszClass = AfxRegisterWndClass(0); return TRUE; } /////////////////////////////////////////////////////////////////////////// // CMainFrame diagnostics #ifdef _DEBUG void CMainFrame::AssertValid() const { CFrameWnd::AssertValid(); } void CMainFrame::Dump(CDumpContext& dc) const { CFrameWnd::Dump(dc); } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////// // CMainFrame message handlers void CMainFrame::OnSetFocus(CWnd* pOldWnd) { // forward focus to the view window m_wndView.SetFocus(); } BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { // let the view have first crack at the command if (m_wndView.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise, do default handling return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo); } int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if (!m_wndView.Create(NULL, NULL, AFX_WS_DEFAULT_VIEW, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, NULL)) { TRACE0("Failed to create view window\n"); return -1; } return 0; } |
ChildView.h
// ChildView.h : interface of the CChildView class // /////////////////////////////////////////////////////////////////////////// #if !defined(AFX_CHILDVIEW_H__0FA1D28A_8471_11D2_8E53_006008A82731__INCLUDED_) #define AFX_CHILDVIEW_H__0FA1D28A_8471_11D2_8E53_006008A82731__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 /////////////////////////////////////////////////////////////////////////// // CChildView window class CChildView : public CWnd { // Construction public: CChildView(); // Attributes public: // Operations public: // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL // Implementation public: virtual ~CChildView(); // Generated message map functions protected: BOOL IsTextSelected (); CEdit m_wndEdit; //AFX_MSG DECLARE_MESSAGE_MAP() }; /////////////////////////////////////////////////////////////////////////// // // Microsoft Visual C++ will insert additional declarations // immediately before the previous line. #endif // !defined(AFX_CHILDVIEW_H__0FA1D28A_8471_11D2_8E53_006008A82731__INCLUDED_) |
ChildView.cpp
// ChildView.cpp : implementation of the CChildView class // #include "stdafx.h" #include "MyPad.h" #include "ChildView.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif /////////////////////////////////////////////////////////////////////////// // CChildView CChildView::CChildView() { } CChildView::~CChildView() { } BEGIN_MESSAGE_MAP(CChildView,CWnd ) //AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////////////////////// // CChildView message handlers BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; cs.dwExStyle ¦= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW¦CS_VREDRAW¦CS_DBLCLKS, ::LoadCursor(NULL, IDC_ARROW), HBRUSH(COLOR_WINDOW+1), NULL); return TRUE; } void CChildView::OnPaint() { CPaintDC dc(this); } int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CWnd ::OnCreate(lpCreateStruct) == -1) return -1; m_wndEdit.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_VSCROLL ¦ ES_MULTILINE ¦ ES_AUTOVSCROLL, CRect (0, 0, 0, 0), this, IDC_EDIT); return 0; } void CChildView::OnSize(UINT nType, int cx, int cy) { CWnd ::OnSize(nType, cx, cy); m_wndEdit.MoveWindow (0, 0, cx, cy); } void CChildView::OnSetFocus(CWnd* pOldWnd) { m_wndEdit.SetFocus (); } void CChildView::OnEditCut() { m_wndEdit.Cut (); } void CChildView::OnEditCopy() { m_wndEdit.Copy (); } void CChildView::OnEditPaste() { m_wndEdit.Paste (); } void CChildView::OnEditDelete() { m_wndEdit.Clear (); } void CChildView::OnEditUndo() { m_wndEdit.Undo (); } void CChildView::OnUpdateEditCut(CCmdUI* pCmdUI) { pCmdUI->Enable (IsTextSelected ()); } void CChildView::OnUpdateEditCopy(CCmdUI* pCmdUI) { pCmdUI->Enable (IsTextSelected ()); } void CChildView::OnUpdateEditPaste(CCmdUI* pCmdUI) { pCmdUI->Enable (::IsClipboardFormatAvailable (CF_TEXT)); } void CChildView::OnUpdateEditDelete(CCmdUI* pCmdUI) { pCmdUI->Enable (IsTextSelected ()); } void CChildView::OnUpdateEditUndo(CCmdUI* pCmdUI) { pCmdUI->Enable (m_wndEdit.CanUndo ()); } void CChildView::OnFileNew() { m_wndEdit.SetWindowText (_T ("")); } BOOL CChildView::IsTextSelected() { int nStart, nEnd; m_wndEdit.GetSel (nStart, nEnd); return (nStart != nEnd); } |
The CComboBox Class
The combo box combines a single-line edit control and a list box into one convenient package. Combo boxes come in three varieties: simple, drop-down, and drop-down list. Figure 7-7 shows a drop-down list combo box with its list displayed.
Figure 7-7. A combo box with a drop-down list displayed.
Simple combo boxes are the least used of the three combo box types. A simple combo box's list box is permanently displayed. When the user selects an item from the list, that item is automatically copied to the edit control. The user can also type text directly into the edit control. If the text the user enters matches an item in the list box, the item is automatically highlighted and scrolled into view.
A drop-down combo box differs from a simple combo box in that its list box is displayed only on demand. A drop-down list combo box works the same way but doesn't allow text to be typed into the edit control. This restriction effectively limits the user's selection to items appearing in the list box.
The style flags you pass to Create or CreateEx determine what type of combo box you create. CBS_SIMPLE creates a simple combo box, CBS_DROPDOWN creates a drop-down combo box, and CBS_DROPDOWNLIST creates a drop-down list combo box. Other styles control additional aspects of the combo box's appearance and behavior, as shown in the table below. Many of these styles will look familiar because they're patterned after list box and edit control styles. CBS_AUTOHSCROLL, for example, does the same thing for the edit control portion of a combo box control that ES_AUTOHSCROLL does for a stand-alone edit control. When you create a combo box control, don't forget to include the style WS_VSCROLL if you want the list box to have a vertical scroll bar and WS_BORDER if you want the control's border to be visible. If m_wndComboBox is a CComboBox object, the statement
m_wndComboBox.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER ¦ WS_VSCROLL ¦ CBS_DROPDOWNLIST ¦ CBS_SORT, rect, this, IDC_COMBOBOX); |
creates a drop-down list combo box whose list box contains a vertical scroll bar when the number of items in the list box exceeds the number of items that can be displayed and that automatically sorts the items added to it. The control rectangle you specify in the call to CComboBox::Create should be large enough to encompass the list box part of the control as well as the edit box.
Combo Box Styles
Style | Description |
---|---|
CBS_AUTOHSCROLL | Enables horizontal scrolling in the edit control portion of a combo box. |
CBS_DISABLENOSCROLL | Disables the combo box list box's scroll bar when it isn't needed. Without this style, an unneeded scroll bar is hidden rather than disabled. |
CBS_DROPDOWN | Creates a drop-down combo box. |
CBS_DROPDOWNLIST | Creates a drop-down list combo box. |
CBS_HASSTRINGS | Creates a combo box that "remembers" the strings added to it. Conventional combo boxes have this style by default; owner-draw combo boxes don't. |
CBS_LOWERCASE | Forces all text in the combo box to lowercase. |
CBS_NOINTEGRALHEIGHT | Prevents the combo box's list box height from having to be an exact multiple of the item height. |
CBS_OEMCONVERT | A combo box whose edit control performs an ANSI-to-OEM-to-ANSI conversion on all characters so that the application won't get unexpected results if it performs an ANSI-to-OEM conversion of its own. Obsolete. |
CBS_OWNERDRAWFIXED | Creates an owner-draw combo box whose items are all the same height. |
CBS_OWNERDRAWVARIABLE | Creates an owner-draw combo box whose items can vary in height. |
CBS_SIMPLE | Creates a simple combo box. |
CBS_SORT | Automatically sorts items as they are added. |
CBS_UPPERCASE | Forces all text in the combo box to uppercase. |
Not surprisingly, the list of CComboBox member functions reads a lot like the list of member functions for CEdit and CListBox. Items are added to a combo box, for example, with CComboBox::AddString and CComboBox::InsertString, and the maximum character count for a combo box's edit control is set with CComboBox::LimitText. The GetWindowText and SetWindowText functions that CComboBox inherits from CWnd get and set the text in the edit control. Functions unique to combo boxes include GetLBText, which retrieves the text of an item identified by a 0-based index; GetLBTextLen, which returns the length of an item, in characters; ShowDropDown, which hides or displays the drop-down list box; and GetDroppedState, which returns a value indicating whether the drop-down list is currently displayed.
Combo Box Notifications
Combo boxes send notifications to their parents much as edit controls and list boxes do. The following table lists the notifications the parent can expect, the corresponding MFC message-map macros, and the types of combo boxes the notifications apply to.
Combo Box Notifications
Notification | Message-Macro Map | Simple | Drop-Down | Drop-Down List |
---|---|---|---|---|
CBN_DROPDOWN Sent when the drop-down list is displayed. | ON_CBN_DROPDOWN | √ | √ | |
CBN_CLOSEUP Sent when the drop-down list is closed. | ON_CBN_CLOSEUP | √ | √ | |
CBN_DBLCLK Sent when an item is double-clicked. | ON_CBN_DBLCLK | √ | ||
CBN_SELCHANGE Sent when the selection changes. | ON_CBN_SELCHANGE | √ | √ | √ |
CBN_SELENDOK Sent when a selection is made. | ON_CBN_SELENDOK | √ | √ | √ |
CBN_SELENDCANCEL Sent when a selection is canceled. | ON_CBN_SELENDCANCEL | √ | √ | |
CBN_EDITUPDATE Sent when the text in the edit control is about to change. | N_CBN_EDITUPDATE | √ | √ | |
CBN_EDITCHANGE Sent when the text in the edit control has changed. | ON_CBN_EDITCHANGE | √ | √ | |
CBN_KILLFOCUS Sent when the combo box loses the input focus. | ON_CBN_KILLFOCUS | √ | √ | √ |
CBN_SETFOCUS Sent when the combo box receives the input focus. | ON_CBN_SETFOCUS | √ | √ | √ |
CBN_ERRSPACE Sent when an operation fails because of insufficient memory. | ON_CBN_ERRSPACE | √ | √ | √ |
Not all notifications apply to all combo box types. CBN_DROPDOWN and CBN_CLOSEUP notifications, for example, aren't sent to CBS_SIMPLE combo boxes because a simple combo box's list box doesn't open and close. By the same token, CBS_DROPDOWN and CBS_DROPDOWNLIST-style combo boxes don't receive CBN_DBLCLK notifications because the items in their lists can't be double-clicked. (Why? Because the list box closes after the first click.) CBN_EDITUPDATE and CBN_EDITCHANGE notifications are equivalent to EN_UPDATE and EN_CHANGE notifications sent by edit controls, and CBN_SELCHANGE is to combo boxes as LBN_SELCHANGE is to list boxes.
One nuance you should be aware of when processing CBN_SELCHANGE notifications is that when a notification arrives, the edit control might not have been updated to match the list box selection. Therefore, you should use GetLBText to retrieve the newly selected text instead of GetWindowText. You can get the index of the selected item with CComboBox::GetCurSel.
The CScrollBar Class
MFC's CScrollBar class encapsulates scroll bar controls created from the "SCROLLBAR" WNDCLASS. Scroll bar controls are identical in most respects to the "window" scroll bars used in Chapter 2's Accel application. But whereas window scroll bars are created by adding WS_VSCROLL and WS_HSCROLL flags to the window style, scroll bar controls are created explicitly with CScrollBar::Create. And though a window scroll bar runs the full length of the window's client area and is inherently glued to the window border, scroll bar controls can be placed anywhere in the window and can be set to any height and width.
You create vertical scroll bars by specifying the style SBS_VERT and horizontal scroll bars by specifying SBS_HORZ. If m_wndVScrollBar and m_wndHScrollBar are CScrollBar objects, the statements
m_wndVScrollBar.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER ¦ SBS_VERT, rectVert, this, IDC_VSCROLLBAR); m_wndHScrollBar.Create (WS_CHILD ¦ WS_VISIBLE ¦ WS_BORDER ¦ SBS_HORZ, rectHorz, this, IDC_HSCROLLBAR); |
create two scroll bar controls, one vertical and the other horizontal.
You can query Windows for the standard width of a vertical scroll bar or the standard height of a horizontal scroll bar with the ::GetSystemMetrics API function. The following code fragment sets nWidth and nHeight to the system's standard scroll bar width and height:
int nWidth = ::GetSystemMetrics (SM_CXVSCROLL); int nHeight = ::GetSystemMetrics (SM_CYHSCROLL); |
An alternative method for creating a scroll bar with a standard height or width is to specify the style SBS_TOPALIGN, SBS_BOTTOMALIGN, SBS_LEFTALIGN, or SBS_RIGHTALIGN when creating it. SBS_LEFTALIGN and SBS_RIGHTALIGN align a vertical scroll bar control along the left or right border of the rectangle specified in the call to Create and assign it a standard width. SBS_TOPALIGN and SBS_BOTTOMALIGN align a horizontal scroll bar control along the top or bottom border of the rectangle and assign it a standard height.
Unlike the other classic controls, scroll bar controls don't send WM_COMMAND messages; they send WM_VSCROLL and WM_HSCROLL messages instead. MFC applications process these messages with OnVScroll and OnHScroll handlers, as described in Chapter 2. I didn't mention two scroll bar notification codes in Chapter 2 because they apply only to scroll bar controls. SB_TOP means that the user pressed the Home key while the scroll bar had the input focus, and SB_BOTTOM means the user pressed End.
MFC's CScrollBar class includes a handful of functions for manipulating scroll bars, most of which should seem familiar to you because they work just like the similarly named CWnd functions. CScrollBar::GetScrollPos and CScrollBar::SetScrollPos get and set the scroll bar's thumb position. CScrollBar::GetScrollRange and CScrollBar::SetScrollRange get and set the scroll bar range. You use CScrollBar::SetScrollInfo to set the range, position, and thumb size in one step. For details, refer to the discussion of CWnd::SetScrollInfo in Chapter 2.