The Ex08a Example: Standard Common Controls
To get an idea of how these common controls work, we'll put them in a modal dialog box. The steps are as follows.
Run the MFC Application Wizard to generate a project named Ex08a. Choose New Project from Visual Studio's File menu. In the New Project dialog box, select the MFC Application template, type the name Ex08a, and click OK. In the MFC Application Wizard, accept all the defaults but two: On the Application Type page, select Single Document, and on the Advanced Features page, deselect Printing And Print Preview. When you're finished, click Finish.
Create a new dialog resource with ID IDD_DIALOG1. Choose Add Resource from the Project menu and add a new dialog resource. Using the Toolbox, add controls to the dialog box. The following is a list of the control types, their IDs, and their tab order. After you set the Caption properties to the appropriate text, the dialog box should have the following controls and tab order:

Don't worry about the other properties now—you'll set those in the following steps. (Some controls might look different than they do in Figure 8-1 until you set their properties.)
Control Type | ID | Tab Order |
---|---|---|
Static Text | IDC_STATIC | 1 |
Progress | IDC_PROGRESS1 | 2 |
Static Text | IDC_STATIC | 3 |
Slider | IDC_SLIDER1 | 4 |
Static Text | IDC_STATIC_SLIDER1 | 5 |
Static Text | IDC_STATIC | 6 |
Slider | IDC_SLIDER2 | 7 |
Static Text | IDC_STATIC_SLIDER2 | 8 |
Static Text | IDC_STATIC | 9 |
Edit | IDC_BUDDY_SPIN1 | 10 |
Spin | IDC_SPIN1 | 11 |
Static Text | IDC_STATIC | 12 |
Static Text | IDC_STATIC | 13 |
List control | IDC_LISTVIEW1 | 14 |
Static Text | IDC_STATIC_LISTVIEW1 | 15 |
Static Text | IDC_STATIC | 16 |
Tree control | IDC_TREEVIEW1 | 17 |
Static Text | IDC_STATIC_TREEVIEW1 | 18 |
Button | IDOK | 19 |
Button | IDCANCEL | 20 |
Use the MFC Class Wizard to create a new class, CEx08aDialog, derived from CDialog. Choose Add Class from Project menu to display the MFC Class Wizard. Select CDialog as the base class and IDD_DIALOG1 as the name of the Dialog ID, as shown here:

Override the OnInitDialog function and handle the WM_HSCROLL and the WM_VSCROLL messages. Select the CEx08aDialog class in Class View. Click the Overrides button at the top of the Properties window and add the OnInitDialog function. Click the Messages button at the top of the Properties window and add the OnHScroll and OnVScroll functions for the WM_HSCROLL and WM_VSCROLL messages.
Program the progress control. Visual Studio won't generate a data member for this control, so you must do it yourself. Add a public integer data member named m_nProgress in the CEx08aDialog class header, and set it to 0 in the constructor. Also, add the following code in the OnInitDialog member function:
// Progress control
CProgressCtrl* pProg =
(CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);
pProg->SetRange(0, 100);
pProg->SetPos(m_nProgress);
Program the "continuous" slider control. Add a public integer data member named m_nSlider1 to the CEx08aDialog header, and set it to 0 in the constructor. Then add the following code in the OnInitDialog member function to set the slider's range, initialize its position from the data member, and set the neighboring static control to the slider's current value:
// Slider control
CString strText1;
CSliderCtrl* pSlide1 =
(CSliderCtrl*) GetDlgItem(IDC_SLIDER1);
pSlide1->SetRange(0, 100);
pSlide1->SetPos(m_nSlider1);
strText1.Format("%d", pSlide1->GetPos());
SetDlgItemText(IDC_STATIC_SLIDER1, strText1);
To keep the static control updated, you must map the WM_HSCROLL message that the slider sends to the dialog box. Add the following boldface code to the OnHScroll handler, replacing the existing code:
void CEx08aDialog::OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;
strText.Format("%d", pSlide->GetPos());
SetDlgItemText(IDC_STATIC_SLIDER1, strText);
}
Finally, you need to update the slider's m_nSlider1 data member when the user clicks OK. Your natural instinct might be to put this code in the OnOK button handler. You would have a problem, however, if a data exchange validation error occurred that involved any other control in the dialog box. Your handler would set m_nSlider1 even if the user chose to cancel the dialog box. To avoid this problem, add your code in the DoDataExchange function as shown below. If you do your own validation and detect a problem, call the CDataExchange::Fail function, which alerts the user with a message box.
void CEx08aDialog::DoDataExchange(CDataExchange* pDX)
{
if (pDX->m_bSaveAndValidate) {
TRACE("updating slider data members\n");
CSliderCtrl* pSlide1 =
(CSliderCtrl*) GetDlgItem(IDC_SLIDER1);
m_nSlider1 = pSlide1->GetPos();
}
CDialog::DoDataExchange(pDX);
}
Program the "discrete" slider control. Add a public integer data member named m_nSlider2 to the CEx08aDialog header, and set it to 0 in the constructor. This data member is a zero-based index into the dValue, the array of numbers (4.0, 5.6, 8.0, 11.0, and 16.0) that the slider can represent. Define dValue as a public static double array member variable in

static double dValue[5];
Initialize dValue at the top of

double CEx08aDialog::dValue[5] = {4.0, 5.6, 8.0, 11.0, 16.0};
Next, add code in the OnInitDialog member function to set the slider's range and initial position.
CString strText2;
CSliderCtrl* pSlide2 =
(CSliderCtrl*) GetDlgItem(IDC_SLIDER2);
pSlide2->SetRange(0, 4);
pSlide2->SetPos(m_nSlider2);
strText2.Format("%3.1f", dValue[pSlide2->GetPos()]);
SetDlgItemText(IDC_STATIC_SLIDER2, strText2);
If you had only one slider, the WM_HSCROLL handler in step 5 would work. But because you have two sliders that send WM_HSCROLL messages, the handler must differentiate between them. Here's the new code:
void CEx08aDialog::OnHScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar;
CString strText;
// Two sliders are sending
// HSCROLL messages (different processing)
switch(pScrollBar->GetDlgCtrlID()) {
case IDC_SLIDER1:
strText.Format("%d", pSlide->GetPos());
SetDlgItemText(IDC_STATIC_SLIDER1, strText);
break;
case IDC_SLIDER2:
strText.Format("%3.1f", dValue[pSlide->GetPos()]);
SetDlgItemText(IDC_STATIC_SLIDER2, strText);
break;
}
}
Slider2 needs tick marks, so display the dialog editor and set the control's Tick Marks and Auto Ticks properties to True in the Properties window. With Auto Ticks set to True, the slider will place a tick at every increment. If you don't see the tick marks after setting these properties, you might need to increase the height of the control.The same data exchange considerations that applied to the previous slider apply to this slider. Add the following code in the dialog class DoDataExchange member function inside the block for the if statement you added in the previous step:
CSliderCtrl* pSlide2 =
(CSliderCtrl*) GetDlgItem(IDC_SLIDER2);
m_nSlider2 = pSlide2->GetPos();
Display the dialog editor and set the Point property of both sliders to Bottom/Right. Select the IDC_STATIC_SLIDER1 and IDC_STATIC_SLIDER2 static controls and set the Align Text property to Right.
Program the spin button control. The spin control depends on its buddy edit control, which is located immediately before it in the tab order. Use the Add Member Variable Wizard to add a double-precision data member named m_dSpin for the IDC_BUDDY_SPIN1 edit control. We're using a double instead of an int because the int would require almost no programming, and that would be too easy. We want the edit control range to be 0.0 to 10.0, but the spin control itself needs an integer range. You can start the Add Member Variable Wizard by selecting the CEx08aDialog class in Class View and then choosing Add Variable from the Project menu. The settings for the wizard are shown here:

Add the following code to OnInitDialog to set the spin control range from 0 to 100 and set its initial value to m_dSpin * 10.0:
// Spin control
CSpinButtonCtrl* pSpin =
(CSpinButtonCtrl*) GetDlgItem(IDC_SPIN1);
pSpin->SetRange(0, 100);
pSpin->SetPos((int) (m_dSpin * 10.0));
To display the current value in the buddy edit control, you need to handle the WM_VSCROLL message that the spin control sends to the dialog box. Add the following boldfaced code to OnVScroll:
void CEx08aDialog::OnVScroll(UINT nSBCode, UINT nPos,
CScrollBar* pScrollBar)
{
if (nSBCode == SB_ENDSCROLL) {
return; // Reject spurious messages
}
// Process scroll messages from IDC_SPIN1 only
if (pScrollBar->GetDlgCtrlID() == IDC_SPIN1) {
CString strValue;
strValue.Format("%3.1f", (double) nPos / 10.0);
((CSpinButtonCtrl*) pScrollBar)->GetBuddy()
->SetWindowText(strValue);
}
CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}
There's no need to add code in OnOK or in DoDataExchange because the Dialog Data Exchange (DDX) code processes the contents of the edit control.Display the dialog editor and set the Auto Buddy property for the spin control to True. Set the Read Only property for the buddy edit control to True.
Set up an image list. Both the list control and the tree control need an image list, and the image list needs icons. The companion CD contains icons in the Ex08a\res folder. These icons are circles with black outlines and different-colored interiors. Use fancier icons if you have them.To import these icons into the Ex08a project, first copy the .ico files to your Ex08a\res folder and then choose Add Resource from the Project menu. In the Add Resource dialog box, click Import. In the Import dialog box, navigate to the icon files. Set the Files Of Type drop-down list to Icon Files. Select


Icon File | ID |
---|---|
![]() | IDI_WHITE |
![]() | IDI_BLACK |
![]() | IDI_RED |
![]() | IDI_BLUE |
![]() | IDI_YELLOW |
![]() | IDI_CYAN |
![]() | IDI_PURPLE |
![]() | IDI_GREEN |
When you're finished, the Icon folder in Resource View will look like the following:

You probably know that a bitmap is an array of bits that represent pixels on the display. (Bitmaps were discussed in Chapter 6.) In Windows, an icon is a "bundle" of bitmaps. First of all, an icon has different bitmaps for different sizes. Typically, small icons are 16 by 16 pixels and large icons are 32 by 32 pixels. Within each size are two separate bitmaps: one 4-bit-per-pixel bitmap for the color image and one monochrome (1-bit-per-pixel) bitmap for the "mask." If a mask bit is 0, the corresponding image pixel represents an opaque color. If the mask bit is 1, an image color of black (0) means that the pixel is transparent and an image color of white (0xF) means that the background color is inverted at the pixel location.Small icons were new with Windows 95. They're used on the taskbar, in Windows Explorer, and in your list and tree controls, if you want them there. If an icon doesn't have a 16-by-16-pixel bitmap, Windows manufactures a small icon out of the 32-by-32-pixel bitmap, but it won't be as neat as one you draw yourself. The image editor in Visual Studio lets you create and edit icons. The following shows the image editor and the Colors palette.

The top square in the upper left portion of the Colors palette shows you the main color for brushes, shape interiors, and so on, and the square underneath it shows the border color for shape outlines. You select a main color by left-clicking on a color, and you select a border color by right-clicking on a color. Now look at the two "monitors" to the right of the upper left square of the Colors palette. You click on the upper monitor to paint transparent pixels, which are drawn in dark cyan. You click on the lower monitor to paint inverted pixels, which are drawn in red.
Next, add a public CImageList data member named m_imageList in the CEx08aDialog class header, and then add the following code to OnInitDialog:
// Icons
HICON hIcon[8];
int n;
m_imageList.Create(16, 16, 0, 8, 8); // 32, 32 for large icons
hIcon[0] = AfxGetApp()->LoadIcon(IDI_WHITE);
hIcon[1] = AfxGetApp()->LoadIcon(IDI_BLACK);
hIcon[2] = AfxGetApp()->LoadIcon(IDI_RED);
hIcon[3] = AfxGetApp()->LoadIcon(IDI_BLUE);
hIcon[4] = AfxGetApp()->LoadIcon(IDI_YELLOW);
hIcon[5] = AfxGetApp()->LoadIcon(IDI_CYAN);
hIcon[6] = AfxGetApp()->LoadIcon(IDI_PURPLE);
hIcon[7] = AfxGetApp()->LoadIcon(IDI_GREEN);
for (n = 0; n < 8; n++) {
m_imageList.Add(hIcon[n]);
}
Program the list control. In the dialog editor, set the following properties for the list control.
List Control Property | Value |
---|---|
Alignment | Top |
Always Show Selection | True |
Single Selection | True |
View | List |
Then add the following code to OnInitDialog:
// List control
static char* color[] = {"white", "black", "red",
"blue", "yellow", "cyan",
"purple", "green"};
CListCtrl* pList =
(CListCtrl*) GetDlgItem(IDC_LISTVIEW1);
pList->SetImageList(&m_imageList, LVSIL_SMALL);
for (n = 0; n < 8; n++) {
pList->InsertItem(n, color[n], n);
}
pList->SetBkColor(RGB(0, 255, 255)); // UGLY!
pList->SetTextBkColor(RGB(0, 255, 255));
As the last two lines illustrate, you don't use the WM_CTLCOLOR message with common controls; you just call a function to set the background color. As you'll see when you run the program, however, the icons' inverse-color pixels look shabby.
If you use the list control's LVN_ITEMCHANGED notification message, you'll be able to track the user's selection of items. In Class View, select the CEx08aDialog class. In the Properties window, click the Events button, expand the IDC_LISTVIEW1 item, select the LVN_ITEMCHANGED event, and then add the OnLvnItemchangedListview1 handler. Add the following code to the OnLvnItemchangedListview1 handler to display the selected item's text in a static control:
void CEx08aDialog::OnLvnItemchangedListview1(NMHDR* pNMHDR,
LRESULT* pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
CListCtrl* pList =
(CListCtrl*) GetDlgItem(IDC_LISTVIEW1);
int nSelected = pNMLV->iItem;
if (nSelected >= 0) {
CString strItem = pList->GetItemText(nSelected, 0);
SetDlgItemText(IDC_STATIC_LISTVIEW1, strItem);
}
*pResult = 0;
}
The NM_LISTVIEW structure has a data member called iItem that contains the index of the selected item.
Program the tree control. In the dialog editor, set the following properties for the tree control.
Tree Control Property | Value |
---|---|
Has Buttons | True |
Has Lines | True |
Lines At Root | True |
Scroll | True |
Next, add the following lines to OnInitDialog:
// Tree control
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
pTree->SetImageList(&m_imageList, TVSIL_NORMAL);
// tree structure common values
TV_INSERTSTRUCT tvinsert;
tvinsert.hParent = NULL;
tvinsert.hInsertAfter = TVI_LAST;
tvinsert.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE |
TVIF_TEXT;
tvinsert.item.hItem = NULL;
tvinsert.item.state = 0;
tvinsert.item.stateMask = 0;
tvinsert.item.cchTextMax = 6;
tvinsert.item.iSelectedImage = 1;
tvinsert.item.cChildren = 0;
tvinsert.item.lParam = 0;
// top level
tvinsert.item.pszText = "Homer";
tvinsert.item.iImage = 2;
HTREEITEM hDad = pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Marge";
HTREEITEM hMom = pTree->InsertItem(&tvinsert);
// second level
tvinsert.hParent = hDad;
tvinsert.item.pszText = "Bart";
tvinsert.item.iImage = 3;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem(&tvinsert);
// second level
tvinsert.hParent = hMom;
tvinsert.item.pszText = "Bart";
tvinsert.item.iImage = 4;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Lisa";
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Dilbert";
HTREEITEM hOther = pTree->InsertItem(&tvinsert);
// third level
tvinsert.hParent = hOther;
tvinsert.item.pszText = "Dogbert";
tvinsert.item.iImage = 7;
pTree->InsertItem(&tvinsert);
tvinsert.item.pszText = "Ratbert";
pTree->InsertItem(&tvinsert);
As you can see, this code sets TV_INSERTSTRUCT text and image indexes and calls InsertItem to add nodes to the tree.Finally, add the TVN_SELCHANGED notification for the tree control. In Class View, select the CEx08aDialog class. In the Properties window, click the Events button, expand the IDC_TREEVIEW1 item, select the TVN_SELCHANGED event, and then add the OnTvnSelchangedTreeview1 handler. Add the following boldface code to display the selected text in a static control:
void CEx08aDialog::OnTvnSelchangedTreeview1 (NMHDR* pNMHDR,
LRESULT* pResult)
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR;
CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);
HTREEITEM hSelected = pNMTreeView->itemNew.hItem;
if (hSelected != NULL) {
char text[31];
TV_ITEM item;
item.mask = TVIF_HANDLE | TVIF_TEXT;
item.hItem = hSelected;
item.pszText = text;
item.cchTextMax = 30;
VERIFY(pTree->GetItem(&item));
SetDlgItemText(IDC_STATIC_TREEVIEW1, text);
}
*pResult = 0;
}
The NM_TREEVIEW structure has a data member called itemNew that contains information about the selected node; itemNew.hItem is the handle of that node. The GetItem function retrieves the node's data, storing the text using a pointer supplied in the TV_ITEM structure. The mask variable tells Windows that the hItem handle is valid going in and that text output is desired.
Add code to the virtual OnDraw function in the file

void CEx08aView::OnDraw(CDC* pDC)
{
CEx08aDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(0, 0, "Press the left mouse button here.");
}
Add the OnLButtonDown member function. Select the CEx08aView class in Class View. In the Properties window, click the Messages button, select the WM_LBUTTONDOWN message, and add the OnLButtonDown function. Add the following boldface code:
void CEx08aView::OnLButtonDown(UINT nFlags, CPoint point)
{
CEx08aDialog dlg;
dlg.m_nSlider1 = 20;
dlg.m_nSlider2 = 2; // index for 8.0
dlg.m_nProgress = 70; // write-only
dlg.m_dSpin = 3.2;
dlg.DoModal();
CView::OnLButtonDown(nFlags, point);
}
In


#include "Ex08aDialog.h"
Compile and run the program. Experiment with the controls to see how they work. We haven't added code to make the progress indicator functional; we'll cover that in Chapter 13.