Programming Windows with MFC, Second Edition

[Previous] [Next]

IP address controls, hotkey controls, month calendar controls, and date-time picker controls all have one characteristic in common: they exist to make it easy to solicit specially formatted input from the user. Some of them, such as the IP address control, are exceedingly simple; others, such as the date-time picker control, offer an intimidating array of options. All are relatively easy to program, however, especially when you use the wrapper classes provided by MFC. The sections that follow provide an overview of all four control types and present code samples demonstrating their use.

IP Address Controls

IP address controls facilitate the effortless entry of 32-bit IP addresses consisting of four 8-bit integer values separated by periods, as in 10.255.10.1. The control accepts numeric input only and is divided into four 3-digit fields, as shown in Figure 16-12. When the user types three digits into a field, the input focus automatically moves to the next field. IP address controls exist only on systems that have Internet Explorer 4.0 or later installed.

Figure 16-12. The IP address control.

MFC codifies the interface to IP address controls with CIPAddressCtrl. CIPAddressCtrl functions named SetAddress and GetAddress get IP addresses in and out. If m_wndIPAddress is a CIPAddressCtrl data member in a dialog class, the following OnInitDialog and OnOK functions initialize the control with the IP address stored in m_nField1 through m_nField4 when the dialog box is created and retrieve the IP address from the control when the dialog box is dismissed:

// In CMyDialog's class declaration BYTE m_nField1, m_nField2, m_nField3, m_nField4; BOOL CMyDialog::OnInitDialog () { CDialog::OnInitDialog (); m_wndIPAddress.SetAddress (m_nField1, m_nField2, m_nField3, m_nField4); return TRUE; } void CMyDialog::OnOK () { m_wndIPAddress.GetAddress (m_nField1, m_nField2, m_nField3, m_nField4); CDialog::OnOK (); }

You can also empty an IP address control with CIPAddressCtrl::ClearAddress or find out whether it is currently empty with CIPAddressCtrl::IsBlank. Another CIPAddressCtrl member function, SetFieldFocus, programmatically moves the input focus to a specified field.

By default, each field in an IP address control accepts a value from 0 to 255. You can change the range of values that a given field will accept with CIPAddressCtrl::SetFieldRange. The following statement configures the control to restrict values entered into the control's first field to 10 through 100 and values entered in the final field to 100 through 155, inclusive:

m_wndIPAddress.SetFieldRange (0, 10, 100); // Field 1 m_wndIPAddress.SetFieldRange (3, 100, 155); // Field 4

The control prevents invalid values from being entered into a field by automatically converting values that fall outside the allowable range to the upper or lower limit of that range, whichever is appropriate.

IP address controls send four types of notifications to their owners. EN_SETFOCUS and EN_KILLFOCUS notifications signify that the control gained or lost the input focus. EN_CHANGE notifications indicate that the data in the control has changed. All three notifications are encapsulated in WM_COMMAND messages. IP address controls also send IPN_FIELDCHANGED notifications when a field's value changes or the input focus moves from one field to another. IPN_FIELDCHANGED is unique among IP address control notifications in that it is transmitted in WM_NOTIFY messages.

Hotkey Controls

Hotkey controls are similar in concept to IP address controls. The chief difference is that hotkey controls accept key combinations instead of IP addresses. A hotkey control is essentially a glorified edit control that automatically converts key combinations such as Ctrl-Alt-P into text strings suitable for displaying on the screen. Hotkey controls are so-called because the key combinations entered in them are sometimes converted into hotkeys with WM_SETHOTKEY messages. Data entered into a hotkey control doesn't have to be used for hotkeys, however; it can be used any way that you, the developer, see fit.

MFC represents hotkey controls with instances of CHotKeyCtrl. Member functions named SetHotKey and GetHotKey convert key combinations into text strings displayed by the control, and vice versa. The following statement initializes a hotkey control represented by the CHotKeyCtrl object m_wndHotkey with the key combination Ctrl-Alt-P. The control responds by displaying the text string "Ctrl + Alt + P":

m_wndHotkey.SetHotKey (_T (`P'), HOTKEYF_CONTROL ¦ HOTKEYF_ALT);

The next two statements read data from the hotkey control into variables named wKeyCode, which holds a virtual key code, and wModifiers, which holds bit flags specifying which, if any, modifier keys—Ctrl, Alt, and Shift—are included in the key combination:

WORD wKeyCode, wModifiers; m_wndHotkey.GetHotKey (wKeyCode, wModifiers);

You can include similar calls to SetHotKey and GetHotKey in a dialog class's OnInitDialog and OnOK functions to transfer data between a hotkey control and data members of the dialog class.

By default, a hotkey control accepts key combinations that include any combination of the Ctrl, Shift, and Alt keys. You can restrict the combinations that the control will accept by calling CHotKeyCtrl::SetRules. SetRules accepts two parameters: an array of bit flags identifying invalid combinations of Ctrl, Shift, and Alt, and an array of bit flags specifying the combination of Ctrl, Shift, and Alt that should replace an invalid combination of modifier keys. For example, the statement

m_wndHotkey.SetRules (HKCOMB_A ¦ HKCOMB_CA ¦ HKCOMB_SA ¦ HKCOMB_SCA, 0);

disallows any key combination that includes the Alt key, and the statement

m_wndHotkey.SetRules (HKCOMB_A ¦ HKCOMB_CA ¦ HKCOMB_SA ¦ HKCOMB_SCA, HOTKEYF_CONTROL);

does the same but also directs the control to replace the modifiers in any key combination that includes the Alt key with the Ctrl key. See the SetRules documentation for a list of other supported HKCOMB flags.

Month Calendar Controls

The month calendar control, which I'll refer to simply as the calendar control, lets users input dates by picking them from a calendar rather than typing them into an edit control. (See Figure 16-13.) A calendar control can support single selections or multiple selections. Clicking a date in a single-selection calendar control makes that date the "current date." In a multiple-selection calendar control, the user can select a single date or a contiguous range of dates. You can set and retrieve the current selection, be it a single date or a range of dates, programmatically by sending messages to the control. MFC wraps these and other calendar control messages in member functions belonging to the CMonthCalCtrl class.

Figure 16-13. The month calendar control.

In a single-selection calendar control, CMonthCalCtrl::SetCurSel sets the current date and CMonthCalCtrl::GetCurSel retrieves it. The statement

m_wndCal.SetCurSel (CTime (1999, 9, 30, 0, 0, 0));

sets the current date to September 30, 1999, in the calendar control represented by m_wndCal. Ostensibly, the statements

CTime date; m_wndCal.GetCurSel (date);

retrieve the date from the control by initializing date with the currently selected date. But watch out. Contrary to what the documentation says, a calendar control sometimes returns random data in the hours, minutes, seconds, and milliseconds fields of the SYSTEMTIME structure it uses to divulge dates in response to MCM_GETCURSEL messages. Because CTime factors the time into the dates it obtains from SYSTEMTIME structures, incrementing the day by 1, for example, if hours equals 25, CTime objects initialized by CMonthCalCtrl::GetCurSel can't be trusted. The solution is to retrieve the current date by sending the control an MCM_GETCURSEL message and zeroing the time fields of the SYSTEMTIME structure before converting it into a CTime, as demonstrated here:

SYSTEMTIME st; m_wndCal.SendMessage (MCM_GETCURSEL, 0, (LPARAM) &st); st.wHour = st.wMinute = st.wSecond = st.wMilliseconds = 0; CTime date (st);

If you prefer, you can also use CMonthCalCtrl's SetRange function to place upper and lower bounds on the dates that the control will allow the user to select.

The alternative to SetCurSel and GetCurSel is to use DDX to get dates in and out of a calendar control. MFC includes a DDX function named DDX_MonthCalCtrl that you can put in a dialog's DoDataExchange function to automatically transfer data between a calendar control and a CTime or COleDateTime data member. It even includes DDV functions for date validation. But guess what? DDX_MonthCalCtrl doesn't work because it uses GetCurSel to read the current date. Until this bug is fixed, your best recourse is to forego DDX and use the techniques described above to get and set the current date.

You can create a calendar control that allows the user to select a range of contiguous dates by including an MCS_MULTISELECT bit in the control's style. By default, a selection can't span more than 7 days. You can change that with CMonthCalCtrl::SetMaxSelCount. The statement

m_wndCal.SetMaxSelCount (14);

sets the upper limit on selection ranges to 14 days. The complementary GetMaxSelCount function returns the current maximum selection count.

To programmatically select a date or a range of dates in a multiple-selection calendar control, you must use CMonthCalCtrl::SetSelRange instead of CMonthCalCtrl::SetCurSel. (The latter fails if it's called on a multiple-selection calendar control.) The statements

m_wndCal.SetSelRange (CTime (1999, 9, 30, 0, 0, 0), CTime (1999, 9, 30, 0, 0, 0));

select September 30, 1999, in an MCS_MULTISELECT-style calendar control, and the statements

m_wndCal.SetSelRange (CTime (1999, 9, 16, 0, 0, 0), CTime (1999, 9, 30, 0, 0, 0));

select September 16 through September 30. This call will fail unless you first call SetMaxSelCount to set the maximum selection range size to 15 days or higher. To read the current selection, use CMonthCalCtrl::GetSelRange as demonstrated here:

CTime dateStart, dateEnd; m_wndCal.GetSelRange (dateStart, dateEnd);

This example sets dateStart equal to the selection's start date and dateEnd to the end date. If just one day is selected, dateStart will equal dateEnd. Fortunately, GetSelRange doesn't suffer from the randomness problems that GetCurSel does.

Three calendar control styles allow you to alter a calendar control's appearance. MCS_NOTODAY removes the line that displays today's date at the bottom of the calendar; MCS_NOTODAYCIRCLE removes the circle that appears around today's date in the body of the calendar; and MCS_WEEKNUMBERS displays week numbers (1 through 52). You can further modify a calendar's appearance with CMonthCalCtrl functions. For example, you can change today's date (as displayed by the control) with SetToday, the day of the week that appears in the calendar's leftmost column with SetFirstDayOfWeek, and the control's colors with SetColor. You can even command the control to display certain dates in boldface type by calling its SetDayState function or processing MCN_GETDAYSTATE notifications. Be aware that SetDayState works (and MCN_GETDAYSTATE notifications are sent) only if MCS_DAYSTATE is included in the control style.

If you'd like to know when the current date (or date range) changes in a calendar control, you can process either of two notifications. MCN_SELECT notifications are sent when the user selects a new date or range of dates. MCN_SELCHANGE notifications are sent when the user explicitly makes a selection and when the selection changes because the user scrolled the calendar backward or forward a month. In an MFC application, you can map these notifications to member functions in the parent window class with ON_NOTIFY or reflect them to functions in a derived control class with ON_NOTIFY_REFLECT.

Date-Time Picker Controls

Date-time picker controls, or DTP controls, provide developers with a simple, convenient, and easy-to-use means for soliciting dates and times from a user. A DTP control resembles an edit control, but rather than display ordinary text strings, it displays dates and times. Dates can be displayed in short format, as in 9/30/99, or long format, as in Thursday, September 30, 1999. Times are displayed in standard HH:MM:SS format followed by AM or PM. Custom date and time formats are also supported. Times and dates can be edited visually—for example, by clicking the control's up and down arrows or picking from a drop-down calendar control—or manually. MFC simplifies the interface to DTP controls with the wrapper class named CDateTimeCtrl.

Using a DTP control to solicit a time requires just one or two lines of code. First you assign the control the style DTS_TIMEFORMAT to configure it to display times rather than dates. Then you call CDateTimeCtrl::SetTime to set the time displayed in the control and CDateTimeCtrl::GetTime when you're ready to retrieve it. Assuming m_wndDTP is a CDateTimeCtrl data member in a dialog class and that m_wndDTP is mapped to a DTP control in the dialog, the following OnInitDialog and OnOK functions transfer data between the control and a CTime member variable in the dialog class:

// In CMyDialog's class declaration CTime m_time; BOOL CMyDialog::OnInitDialog () { CDialog::OnInitDialog (); m_wndDTP.SetTime (&m_time); return TRUE; } void CMyDialog::OnOK () { m_wndDTP.GetTime (m_time); CDialog::OnOK (); }

Rather than call SetTime and GetTime explicitly, you can use a DDX_DateTimeCtrl statement in the dialog's DoDataExchange function instead:

DDX_DateTimeCtrl (pDX, IDC_DTP, m_time);

If you use DDX_DateTimeCtrl to connect a DTP control to a dialog data member, you might also want to use MFC's DDV_MinMaxDateTime function to validate times retrieved from the control.

To display dates rather than times in a DTP control, replace DTS_TIMEFORMAT with either DTS_SHORTDATEFORMAT for short dates or DTS_LONGDATEFORMAT for long dates. You set and retrieve dates the same way you do times: with SetTime and GetTime or DDX_DateTimeCtrl. You can use CDateTimeCtrl::SetRange to limit the dates and times that a DTP control will accept.

A DTP control whose style includes DTS_UPDOWN has up and down arrows that the user can use to edit times and dates. If DTS_UPDOWN is omitted from the control style, a downward-pointing arrow similar to the arrow in a combo box replaces the up and down arrows. Clicking the downward-pointing arrow displays a drop-down calendar control, as illustrated in Figure 16-14. Thus, combining either of the date styles (DTS_SHORTDATEFORMAT or DTS_LONGDATEFORMAT) with DTS_UPDOWN produces a DTP control in which dates are entered using up and down arrows; using either of the date styles without DTS_UPDOWN creates a control in which dates are picked from a calendar. By default, a calendar dropped down from a DTP control is left-aligned with the control. You can alter the alignment by including DTS_RIGHTALIGN in the control style. You can also use the DTS_APPCANPARSE style to allow the user to manually edit the text displayed in a DTP control. Even without this style, the keyboard's arrow keys can be used to edit time and date entries.

Figure 16-14. Date-time picker controls with and without the style DTS_UPDOWN.

CDateTimeCtrl's SetFormat function assigns custom formatting strings to a DTP control. For example, a formatting string of the form "H': `mm': `ss" programs a DTP control to display the time in 24-hour military format. Here's how SetFormat would be used to apply this formatting string:

m_wndDTP.SetFormat (_T ("H\':\'mm\':\'ss"));

In a formatting string, H represents a one-digit or two-digit hour in 24-hour format, mm represents a two-digit minute, and ss represents a two-digit second. The following table shows all the special characters that you can use in formatting strings. You can include literals, such as the colons in the example above, by enclosing them in single quotation marks. If you really want to get fancy, you can use Xs to define callback fields. A DTP control uses DTN_FORMAT and DTN_FORMATQUERY notifications to determine what to display in a callback field, enabling an application that processes these notifications to provide text to a DTP control at run time.

DTP Formatting Characters

Character(s) Description
d One-digit or two-digit day
dd Two-digit day
ddd Three-character day of the week abbreviation (for example, Mon or Tue)
dddd Full day of the week name (for example, Monday or Tuesday)
h One-digit or two-digit hour in 12-hour format
hh Two-digit hour in 12-hour format
H One-digit or two-digit hour in 24-hour format
HH Two-digit hour in 24-hour format
m One-digit or two-digit minute
mm Two-digit minute
M One-digit or two-digit month
MM Two-digit month
MMM Three-character month abbreviation (for example, Jan or Feb)
MMMM Full month name (for example, January or February)
s One-digit or two-digit second
ss Two-digit second
t Displays A for a.m. or P for p.m.
tt Displays AM for a.m. or PM for p.m.
X Callback field
y One-digit year
yy Two-digit year
yyyy Four-digit year

DTP controls send a variety of other notifications to their parents. If you want to know when a drop-down calendar control is displayed, listen for DTN_DROPDOWN notifications. When a DTN_DROPDOWN notification arrives, you can call CDateTimeCtrl::GetMonthCalCtrl to acquire a CMonthCalCtrl pointer that you can use to modify the calendar control. If you simply want to know when the time or the date in a DTP control changes, process DTN_DATETIMECHANGE notifications. Consult the Platform SDK documentation on DTP controls for details concerning these and other DTP control notifications.

Категории