I won't try to contrive a business-oriented example that uses all the custom controls. I'll just slap the controls in a modal dialog and trust that you'll see what's going on. The steps are shown below. After step 3, the instructions are oriented to the individual controls rather than to the Visual C++ components you'll be using.
You can select the controls from the control palette. The following table lists the control types and their IDs.
Don't worry about the other properties nowyou'll set those in the following steps. (Some controls might look different than they do in Figure 6-2 until you set their properties.) Set the tab order as shown next.
Tab Sequence | Control Type | Child Window ID |
1 | Static | IDC_STATIC |
2 | Progress | IDC_PROGRESS1 |
3 | Static | IDC_STATIC |
4 | Trackbar (Slider) | IDC_TRACKBAR1 |
5 | Static | IDC_STATIC_TRACK1 |
6 | Static | IDC_STATIC |
7 | Trackbar (Slider) | IDC_TRACKBAR2 |
8 | Static | IDC_STATIC_TRACK2 |
9 | Static | IDC_STATIC |
10 | Edit | IDC_BUDDY_SPIN1 |
11 | Spin | IDC_SPIN1 |
12 | Static | IDC_STATIC |
13 | Static | IDC_STATIC |
14 | List control | IDC_LISTVIEW1 |
15 | Static | IDC_STATIC_LISTVIEW1 |
16 | Static | IDC_STATIC |
17 | Tree control | IDC_TREEVIEW1 |
18 | Static | IDC_STATIC_TREEVIEW1 |
19 | Pushbutton | IDOK |
20 | Pushbutton | IDCANCEL |
CProgressCtrl* pProg = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS1); pProg->SetRange(0, 100); pProg->SetPos(m_nProgress);
CString strText1; CSliderCtrl* pSlide1 = (CSliderCtrl*) GetDlgItem(IDC_TRACKBAR1); pSlide1->SetRange(0, 100); pSlide1->SetPos(m_nTrackbar1); strText1.Format("%d", pSlide1->GetPos()); SetDlgItemText(IDC_STATIC_TRACK1, strText1);
To keep the static control updated, you need to map the WM_HSCROLL message that the trackbar sends to the dialog. Here is the code for the handler:
void CEx06bDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar; CString strText; strText.Format("%d", pSlide->GetPos()); SetDlgItemText(IDC_STATIC_TRACK1, strText); }
Finally, you need to update the trackbar's m_nTrackbar1 data member when the user clicks OK. Your natural instinct would be to put this code in the OnOK button handler. You would have a problem, however, if a data exchange validation error occurred involving any other control in the dialog. Your handler would set m_nTrackbar1 even though the user might choose to cancel the dialog. 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.
if (pDX->m_bSaveAndValidate) { TRACE("updating trackbar data members\n"); CSliderCtrl* pSlide1 = (CSliderCtrl*) GetDlgItem(IDC_TRACKBAR1); m_nTrackbar1 = pSlide1->GetPos(); }
double CEx06bDialog::dValue[5] = {4.0, 5.6, 8.0, 11.0, 16.0};
Next add code in the OnInitDialog member function to set the trackbar's range and initial position.
CString strText2; CSliderCtrl* pSlide2 = (CSliderCtrl*) GetDlgItem(IDC_TRACKBAR2); pSlide2->SetRange(0, 4); pSlide2->SetPos(m_nTrackbar2); strText2.Format("%3.1f", dValue[pSlide2->GetPos()]); SetDlgItemText(IDC_STATIC_TRACK2, strText2);
If you had only one trackbar, the WM_HSCROLL handler in step 5 would work. But because you have two trackbars that send WM_HSCROLL messages, the handler must differentiate. Here is the new code:
void CEx06bDialog::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) { CSliderCtrl* pSlide = (CSliderCtrl*) pScrollBar; CString strText; // Two trackbars are sending // HSCROLL messages (different processing) switch(pScrollBar->GetDlgCtrlID()) { case IDC_TRACKBAR1: strText.Format("%d", pSlide->GetPos()); SetDlgItemText(IDC_STATIC_TRACK1, strText); break; case IDC_TRACKBAR2: strText.Format("%3.1f", dValue[pSlide->GetPos()]); SetDlgItemText(IDC_STATIC_TRACK2, strText); break; } }
This trackbar needs tick marks, so you must check the control's Tick Marks and Auto Ticks properties back in the dialog editor. With Auto Ticks set, the trackbar will place a tick at every increment. The same data exchange considerations applied to the previous trackbar apply to this trackbar. 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_TRACKBAR2); m_nTrackbar2 = pSlide2->GetPos();
Use the dialog editor to set the Point property of both trackbars to Bottom/Right. Select Right for the Align Text property of both the IDC_STATIC_TRACK1 and IDC_STATIC_TRACK2 static controls.
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 map the WM_VSCROLL message that the spin control sends to the dialog. Here's the code:
void CEx06bDialog::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); } }
There's no need for you to add code in OnOK or in DoDataExchange because the dialog data exchange code processes the contents of the edit control. In the dialog editor, select the spin control's Auto Buddy property and the buddy's Read-only property.
First use the graphics editor to add icons to the project's RC file. On the companion CD-ROM, these icons are circles with black outlines and different-colored interiors. Use fancier icons if you have them. You can import an icon by choosing Resource from the Insert menu and then clicking the Import button. For this example, the icon resource IDs are as follows.
Resource ID | Icon File |
IDI_BLACK | Icon1 |
IDI_BLUE | Icon3 |
IDI_CYAN | Icon5 |
IDI_GREEN | Icon7 |
IDI_PURPLE | Icon6 |
IDI_RED | Icon2 |
IDI_WHITE | Icon0 |
IDI_YELLOW | Icon4 |
Next add a private CImageList data member called m_imageList in the CEx06bDialog class header, and then add the following code to OnInitDialog:
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]); }
About IconsYou probably know that a bitmap is an array of bits that represent pixels on the display. (You'll learn more about bitmaps in Chapter 11.) 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. Windows 95 and Windows NT seem to process inverted colors a little differently than Windows 3.x doesthe inverted pixels show up transparent against the desktop, black against a Windows Explorer window background, and white against list and tree control backgrounds. Don't ask me why.
Small icons were new with Windows 95. They're used in the task bar, 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 graphics editor lets you create and edit icons. Look at the color palette shown here.
![]()
The top square in the upper-left portion shows you the main color for brushes, shape interiors, and so on, and the square under 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 top center portion of the color 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.
Make sure the Border style on the More Styles page is set. Next add the following code to OnInitDialog:
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 ClassWizard to map the list control's LVN_ITEMCHANGED notification message, you'll be able to track the user's selection of items. The code in the following handler displays the selected item's text in a static control:
void CEx06bDialog::OnItemchangedListview1(NMHDR* pNMHDR, LRESULT* pResult) { NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR; CListCtrl* pList = (CListCtrl*) GetDlgItem(IDC_LISTVIEW1); int nSelected = pNMListView->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.
Next, add the following lines to OnInitDialog:
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, use ClassWizard to map the TVN_SELCHANGED notification for the tree control. Here is the handler code to display the selected text in a static control:
void CEx06bDialog::OnSelchangedTreeview1(NMHDR* pNMHDR, LRESULT* pResult) { NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)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.
void CEx06bView::OnDraw(CDC* pDC) { pDC->TextOut(0, 0, "Press the left mouse button here."); }
void CEx06bView::OnLButtonDown(UINT nFlags, CPoint point) { CEx06bDialog dlg; dlg.m_nTrackbar1 = 20; dlg.m_nTrackbar2 = 2; // index for 8.0 dlg.m_nProgress = 70; // write-only dlg.m_dSpin = 3.2; dlg.DoModal(); }
Add a statement to include ex06bDialog.h in file ex06bView.cpp.
You've seen most of the common controls that appear on the dialog editor control palette. We've skipped the animation control because this book doesn't cover multimedia, and we've skipped the hot key control because it isn't very interesting. The tab control is interesting, but you seldom use it inside another dialog. Chapter 13 shows you how to construct a tabbed dialog, sometimes known as a property sheet. In Chapter 13, you'll also see an application that is built around the CRichEditView class, which incorporates the Windows rich edit control.