|
|
QA: How can I create a wizard style dialog?
By Daniel Strigl, July 25, 2003.
Print version
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?
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.
Sample application
You can download a sample application
that illustrates how to use this wizard style dialog.
Related resources:
Discuss
Discuss this article.
Here you can write your comments and read comments of other developers.
|