News | Articles | Libraries | Developer Tools | Books | Forum Links | Search   
Sections:
 

QA: How can I use a property sheet to implement a Wizard?

By Joao Paulo Figueira, August 11, 2003.
Print version

Question

How can I use a property sheet to implement a wizard-like dialog?

Answer

Although the documentation says that the PSH_WIZARD flag is supported for property sheets, it does not seem to work as it should. So, you are a bit left on your own if you want to implement a wizard-like dialog. There is already a solution published on this site - see QA: How can I create a wizard style dialog? (http://www.pocketpcdn.com/articles/wizarddlg.html). The approach presented in that article uses a dialog as the container for property pages, and explicitly implements all the navigation and containment. Fortunately, there is a simpler approach that uses property sheets but, before we get to it, let us look into what Pocket PC property sheets have to offer us.

Pocket PC Property Sheets

Figure 1

In an article published elsewhere - see Property sheet callbacks in the Pocket PC 2002 (http://www.codeproject.com/useritems/cepropertysheet.asp) - the property sheet callback mechanism is introduced and made compatible with MFC, through the CCePropertySheet class. By using a customized callback function, one can add the header and footer found in most Settings property sheets (see Figure 1). The header and footer are set up by handling the PSCB_GETTITLE and PSCB_GETLINKTEXT callback messages:

int CALLBACK CCePropertySheet::CePropertySheetCallBack(HWND hWnd, UINT message, LPARAM lParam) { if(message == PSCB_GETLINKTEXT) { if(!m_strLink.IsEmpty()) { LPTSTR pBuf = (LPTSTR)lParam; wcscpy(pBuf, m_strLink); } } else if(message == PSCB_GETTITLE) { if(!m_strTitle.IsEmpty()) { LPTSTR pBuf = (LPTSTR)lParam; wcscpy(pBuf, m_strTitle); } } return (m_pCallBack)(hWnd, message, lParam); }

What this article does not tell you is how the property sheet is assembled by the system, and how you can use this information for your own purposes. The property sheet is a dialog that contains the following items:

  • The property page dialog
  • The header. This is an optional static child control whose ID is 0x3028.
  • The tab control. It is a child control whose ID 0x3020. MFC identifies it as AFX_IDC_TAB_CONTROL
  • The footer. This is an optional rich ink control whose text - set by PSCB_GETLINKTEXT - is interpreted as the parameter of a EM_INSERTLINKS message (see richink.h for more information).

The main idea of this article is that the tab control can be hidden and that wizard-like navigation can be implemented using SetActivePage(), GetActiveIndex() and GetPageCount().

Implementation

We are now prepared to implement a wizard-like dialog using a property sheet (CCeWizard). This class derives from CCePropertySheet, thereby allowing the user to insert headers and footers. The first thing we need to do is to set-up the dialog:

BOOL CCeWizard::OnInitDialog() { BOOL bResult = CCePropertySheet::OnInitDialog(); HWND hWndTab; // // Hide the tab // hWndTab = ::GetDlgItem(m_hWnd, AFX_IDC_TAB_CONTROL); if(hWndTab) ::ShowWindow(hWndTab, SW_HIDE); // // Hide the OK button // ModifyStyle(0, WS_NONAVDONEBUTTON, SWP_NOSIZE); SHDoneButton(m_hWnd, SHDB_HIDE); // // Populate the toolbar // PopulateToolBar(); UpdateControls(); return bResult; }

We will get to the PopulateToolBar and UpdateControls() methods a bit later. Now, you may notice that after hiding the tab control, the line that separates the header from the dialog is gone too. Apparently, this line is a part of the tab control, and thus it is hidden. To circumvent this problem, we have to draw it ourselves, in the OnPaint method:

void CCeWizard::OnPaint() { CPaintDC dc(this); CRect rc; if(!m_strTitle.IsEmpty()) { GetClientRect(&rc); dc.MoveTo(0, 23); dc.LineTo(rc.right, 23); } }

Note that the line is drawn only if there is a title (m_strTitle belongs to the CCePropertySheet class).

Navigation

Now, we have to worry about navigating through the wizard: after hiding the tab control, we must provide a means for the user to flip through the several pages (property pages). The best place to put the control buttons is on the command bar. The CCeWizard class provides two options for placing controls on the command bar (although you can certainly override this functionality): graphic buttons or text buttons. If you want to provide graphic buttons (Figure 2), create a toolbar on the resource editor with at least four buttons: ID_BAR_OK, ID_BAR_CANCEL, ID_BAR_BACK and ID_BAR_NEXT. When you create your CCeWizard object, pass the toolbar id as the second parameter to the constructor. To show text buttons (Figure 3), use 0 as the second parameter on the class constructor, and define the following string resources: IDS_BAR_OK, IDS_BAR_CANCEL, IDS_BAR_BACK and IDS_BAR_NEXT.

Figure 2

Figure 3

Handling the navigation commands is a simple matter. Here is the ID_BAR_BACK handler:

void CCeWizard::OnBarBack() { SetActivePage(GetActiveIndex() - 1); UpdateControls(); }

And now, the ID_BAR_NEXT handler:

void CCeWizard::OnBarNext() { SetActivePage(GetActiveIndex() + 1); UpdateControls(); }

Updating Controls

Now, let's see how to update the wizard's controls. This task is necessary in order to let the application's user know where in the wizard he or she is. This is done in two ways: updating the navigation buttons and reporting the progress in the wizard's header. All of this is achieved in just one method:

void CCeWizard::UpdateControls() { int iIndex = GetActiveIndex(), nPages = GetPageCount(); CToolBarCtrl& rToolBar = m_pWndEmptyCB->GetToolBarCtrl(); CWnd* pWndHdr; // // Set the header text // pWndHdr = GetDlgItem(AFX_IDC_HEADER_CONTROL); if(pWndHdr) { CString strMsg, strHeader; strMsg.Format(_T(" (%d/%d)"), iIndex + 1, nPages); strHeader = m_strTitle + strMsg; pWndHdr->SetWindowText(strHeader); } // // Enable or disable the back and next buttons if needed // rToolBar.EnableButton(ID_BAR_BACK, iIndex > 0); rToolBar.EnableButton(ID_BAR_NEXT, iIndex < nPages - 1); }

Note that the navigation button's state is updated in the same way whether they are graphic or text.

Inserting the Toolbar

Both the graphics and the text toolbar are inserted using just one method:

void CCeWizard::PopulateToolBar() { CCeCommandBar* pCmdBar; pCmdBar = (CCeCommandBar*)m_pWndEmptyCB; if(m_idToolBar) pCmdBar->LoadToolBar(m_idToolBar); else { TBBUTTON tbButton; CString strMenu; memset(&tbButton, 0, sizeof(TBBUTTON)); tbButton.iBitmap = I_IMAGENONE; tbButton.fsState = TBSTATE_ENABLED; tbButton.fsStyle = TBSTYLE_BUTTON | TBSTYLE_AUTOSIZE; strMenu.LoadString(IDS_BAR_OK); tbButton.iString = (int)(LPCTSTR)strMenu; tbButton.idCommand = ID_BAR_OK; pCmdBar->SendMessage(TB_INSERTBUTTON, 0, (LPARAM)&tbButton); strMenu.LoadString(IDS_BAR_CANCEL); tbButton.iString = (int)(LPCTSTR)strMenu; tbButton.idCommand = ID_BAR_CANCEL; pCmdBar->SendMessage(TB_INSERTBUTTON, 1, (LPARAM)&tbButton); strMenu.LoadString(IDS_BAR_BACK); tbButton.iString = (int)(LPCTSTR)strMenu; tbButton.idCommand = ID_BAR_BACK; pCmdBar->SendMessage(TB_INSERTBUTTON, 2, (LPARAM)&tbButton); strMenu.LoadString(IDS_BAR_NEXT); tbButton.iString = (int)(LPCTSTR)strMenu; tbButton.idCommand = ID_BAR_NEXT; pCmdBar->SendMessage(TB_INSERTBUTTON, 3, (LPARAM)&tbButton); } }

And that's it.

Easter Egg

During the writing of this QA, an interesting behaviour of the Pocket PC property sheet was found: when you create CCePropertySheet object with a title and, before calling DoModal() you change its style with:

sheet.m_psh.dwFlags |= PSH_WIZARD;

you get an interesting result (Figure 4): here are the next and back buttons! But where are your property pages?

Figure 4

Sample Code

All the code above is available for download in a test application. This applications shows how to use the several options of the property sheet and the wizard, as well as the Easter Egg. PropSheetLnk.zip - 160K.

Related resources:

Discuss

Discuss this article. Here you can write your comments and read comments of other developers.
Rate this article:     Poor Excellent    
 12345 
© 2001-2005 Pocket PC Developer Network, a division of Spb Software House