QA: How can I create a wizard style dialog?

Daniel Strigl (daniel.strigl@web.de), July 25, 2003.

Question

I was very frustated as I tried to use the property sheet dialog (CPropertySheet) in wizard style and found out that this style is not supported under the Pocket PC implementation of the MFC. So, how can I create my own wizard style dialog?

Sample 1 Sample 2 Sample 3

Answer

The answer is to create a simple dialog with the three buttons "Previous", "Cancel" and "Next/Ready". Inside this dialog just create the property pages as child windows of your wizard dialog and show only the current active property page. The property page class (CPropertyPage) is derived from CDialog and so you can use the property page as a simple modeless window inside the wizard dialog. You can use the wizard dialog like any other dialog, a simple call of the member function DoModal will open and show the wizard dialog.

CPage1 page1; CPage2 page2; CPage3 page3; CCeWizard wizard(_T("Caption")); wizard.AddPage(&page1); wizard.AddPage(&page2); wizard.AddPage(&page3); if (wizard.DoModal() == IDOK) { AfxMessageBox(_T("Ready")); } else { AfxMessageBox(_T("Cancel")); }

This function returns with IDCANCEL when the user press the "Cancel" button of the wizard dialog, otherwise the return value is IDOK.

Before you can call DoModal just add the property pages, that you will display, with the AddPage member function to your wizard dialog. The AddPage function will save all the property pages in an internaly list.

void CCeWizard::AddPage(CPropertyPage* pPage) { ASSERT_VALID(pPage); ASSERT_KINDOF(CPropertyPage, pPage); ASSERT(!::IsWindow(pPage->m_hWnd)); // Add the property page to the list m_wizardPages.Add(pPage); }

Inside the OnInitDialog function the wizard dialog create all the modeless property pages and display the first one. All the other property pages are hidden and will only get visible when necessary.

BOOL CCeWizard::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here SHDoneButton(m_hWnd, SHDB_HIDE); // Remove (hide) the OK button // Create all dialogs (pages) of the wizard ASSERT(m_wizardPages.GetSize() > 0); for (int n = 0; n < m_wizardPages.GetSize(); n++) { CPropertyPage* pPage = m_wizardPages.GetAt(n); ASSERT_VALID(pPage); ASSERT(!::IsWindow(pPage->m_hWnd)); pPage->m_bFullScreen = FALSE; // No FULL SCREEN dialog! pPage->Create(pPage->m_psp.pszTemplate, this); ASSERT(::IsWindow(pPage->m_hWnd)); ASSERT(pPage->GetStyle() & WS_CHILD); pPage->ModifyStyle(~PAGE_WINDOW_STYLE, PAGE_WINDOW_STYLE); ASSERT(pPage->GetStyle() == PAGE_WINDOW_STYLE); } if (m_wizardPages.GetSize() > 0) { ShowPage(0); // Show the first page of the wizard } UpdateWizardButtons(); // Update the wizard buttons return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }

The ShowPage function is a helper function to switch from one property page to another. It will hide the current page and display the next or previous one at the right position in the center of the wizard dialog.

void CCeWizard::ShowPage(int nIndex) { ASSERT(nIndex >= 0 && nIndex < m_wizardPages.GetSize()); ASSERT(m_nSelectPage >= 0 && m_nSelectPage < m_wizardPages.GetSize()); CPropertyPage* pPage = NULL; // Hide the active page if (nIndex != m_nSelectPage) { pPage = m_wizardPages.GetAt(m_nSelectPage); ASSERT_VALID(pPage); pPage->ShowWindow(SW_HIDE); } // Show the selected page pPage = m_wizardPages.GetAt(nIndex); ASSERT_VALID(pPage); CRect rtPage; GetClientRect(&rtPage); rtPage.top += CAPTION_PANEL_HEIGHT; rtPage.bottom -= BUTTONS_PANEL_HEIGHT; // Move the dialog to the correct position pPage->SetWindowPos(NULL, rtPage.left, rtPage.top + 1, rtPage.Width(), rtPage.Height() - 1, SWP_NOZORDER | SWP_SHOWWINDOW); // Save the active page m_nSelectPage = nIndex; }

The UpdateWizardButtons function updates the three buttons "Previous", "Cancel" and "Next/Ready" of the wizard dialog. It will enable/disable the "Previous" button and change the title of the "Next" button to "Ready" when the last property page is visible.

void CCeWizard::UpdateWizardButtons() { // Enable / disable buttons m_btnPrevious.EnableWindow(m_nSelectPage >= 1); // Change icon title if necessary if (m_nSelectPage >= m_wizardPages.GetSize() - 1) { m_btnNext.SetWindowText(_T("Ready")); } else { m_btnNext.SetWindowText(_T("Next >")); } }

The message handler of the three wizard buttons are very simple. The "Cancel" button will close the wizard dialog with the return value IDCANCEL. The "Previous" button switch to the previous property page and the "Next/Ready" button to the next one or will close the wizard dialog with the value IDOK when the last property page was the current active page.

void CCeWizard::OnWizardPrevious() { // TODO: Add your control notification handler code here ASSERT(m_nSelectPage >= 1 && m_nSelectPage <= m_wizardPages.GetSize() - 1); if (m_nSelectPage >= 1 && m_nSelectPage <= m_wizardPages.GetSize() - 1) { CPropertyPage* pPage = m_wizardPages.GetAt(m_nSelectPage); ASSERT_VALID(pPage); // Update dialog (page) data if (pPage->UpdateData()) { // Show the previous page ShowPage(m_nSelectPage - 1); // Update the wizard buttons UpdateWizardButtons(); } } } void CCeWizard::OnWizardCancel() { // TODO: Add your control notification handler code here EndDialog(IDCANCEL); } void CCeWizard::OnWizardNext() { // TODO: Add your control notification handler code here ASSERT(m_nSelectPage >= 0 && m_nSelectPage <= m_wizardPages.GetSize() - 1); if (m_nSelectPage >= 0 && m_nSelectPage <= m_wizardPages.GetSize() - 1) { CPropertyPage* pPage = m_wizardPages.GetAt(m_nSelectPage); ASSERT_VALID(pPage); // Update dialog (page) data if (pPage->UpdateData()) { if (m_nSelectPage >= m_wizardPages.GetSize() - 1) { EndDialog(IDOK); } else // if (m_nSelectPage < m_wizardPages.GetSize() - 1) { // Show the next page ShowPage(m_nSelectPage + 1); // Update the wizard buttons UpdateWizardButtons(); } } } }

Inside the OnSize message, when the wizard dialog will be resized (e.g. when the SIP is visible), the three wizard buttons will be moved to the right position and the current active property page will be resized.

void CCeWizard::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); // TODO: Add your message handler code here Invalidate(); // Move the buttons CRect rtClient, rtBtn; GetClientRect(&rtClient); // Move PREVIOUS button m_btnPrevious.GetWindowRect(&rtBtn); ScreenToClient(&rtBtn.TopLeft()); m_btnPrevious.SetWindowPos(NULL, rtBtn.left, rtClient.bottom - WIZARD_BTNS_DISTANCE, 0, 0, SWP_NOSIZE | SWP_NOZORDER); // Move NEXT / READY button m_btnNext.GetWindowRect(&rtBtn); ScreenToClient(&rtBtn.TopLeft()); m_btnNext.SetWindowPos(NULL, rtBtn.left, rtClient.bottom - WIZARD_BTNS_DISTANCE, 0, 0, SWP_NOSIZE | SWP_NOZORDER); // Move CANCEL button m_btnCancel.GetWindowRect(&rtBtn); ScreenToClient(&rtBtn.TopLeft()); m_btnCancel.SetWindowPos(NULL, rtBtn.left, rtClient.bottom - WIZARD_BTNS_DISTANCE, 0, 0, SWP_NOSIZE | SWP_NOZORDER); // Redraw the page if ((m_wizardPages.GetSize() > m_nSelectPage) && (::IsWindow(m_wizardPages.GetAt(m_nSelectPage)->m_hWnd))) { ShowPage(m_nSelectPage); } }

To paint the caption of the wizard dialog and the line top of the three wizard buttons just add the following code inside the OnPaint message handler.

void CCeWizard::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here // Do not call CDialog::OnPaint() for painting messages CRect rtPage; GetClientRect(&rtPage); // Paint caption CFont* pCurrentFont = dc.GetCurrentFont(); LOGFONT lf; pCurrentFont->GetLogFont(&lf); lf.lfWeight = FW_BOLD; lf.lfHeight = 13; CFont newFont; newFont.CreateFontIndirect(&lf); CFont* pOldFont = dc.SelectObject(&newFont); dc.SetTextColor(RGB(0, 0, 156)); DrawTextEndEllipsis(dc, m_strCaption, CRect(8, 0, rtPage.right - 8, CAPTION_PANEL_HEIGHT - 1), DT_VCENTER | DT_SINGLELINE); dc.SelectObject(pOldFont); // Paint lines CPen blackPen(PS_SOLID, 1, RGB(0,0,0)); CPen *pOldPen = dc.SelectObject(&blackPen); dc.MoveTo(0, CAPTION_PANEL_HEIGHT); dc.LineTo(rtPage.right, CAPTION_PANEL_HEIGHT); dc.MoveTo(0, rtPage.bottom - BUTTONS_PANEL_HEIGHT); dc.LineTo(rtPage.right, rtPage.bottom - BUTTONS_PANEL_HEIGHT); dc.SelectObject(pOldPen); }

When you create your property pages with the resource editor please set the dialog style of the property pages to Child, because any other style will cause some ugly side effects with the SIP and will throw an ASSERT in the OnInitDialog function of the wizard dialog.

Resource editor

Sample application

You can download a sample application that illustrates how to use this wizard style dialog.

Related resources: