Click here to Skip to main content
Click here to Skip to main content

A Wizard-like property sheet for the Pocket PC

, 19 Sep 2003 CPOL
Rate this:
Please Sign up or sign in to vote.
Implementing a wizard-like dialog on the Pocket PC using property sheets.

Sample Image - CeWizard.jpg

Introduction

It is common knowledge that the Pocket PC does not support wizard dialogs. Although the documentation says that the PSH_WIZARD flag is supported for property sheets, it does not seem to work as it should.

This article describes an implementation of a wizard-like dialog supported by property sheets, using a very simple tweak. There are other resources on creating wizard dialogs, most notably Daniel S.'s implementation using a dialog: QA: How can I create a wizard style dialog?. This article is the update of an article I had previously published: QA: How can I use a property sheet to implement a Wizard?.

In this second version of the article, the "visual glitch" reported by John Simmons is solved.

Pocket PC property sheets

In an article published here - see Property sheet callbacks in the Pocket PC 2002 - 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. The header and footer are set up by handling the PSCB_GETTITLE and PSCB_GETLINKTEXT callback messages.

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 is 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:

// CCeWizard::OnInitDialog
//
//        Initializes the wizard property sheet
//
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:

// CCeWizard::OnPaint
//
//        Paints the dialog
//
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 (see top image), 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 (see next image), 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.

Wizard with text buttons

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

// CCeWizard::OnBarBack
//
//        Moves to the previous page
//
void CCeWizard::OnBarBack() 
{
    SetActivePage(GetActiveIndex() - 1);

    UpdateControls();
}

And now, the ID_BAR_NEXT handler:

// CCeWizard::OnBarNext
//
//        Moves to the next page
//
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:

// CCeWizard::UpdateControls
//
//        Updates the command bar buttons
//
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);
    ResizePage();
}

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

Inserting the toolbar

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

// CCeWizard::PopulateToolBar
//
//        Loads a graphics or button toolbar
//
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);
    }
}

Terminating the wizard

Terminating the wizard should not be done using a direct call to EndDialog(). My own experience showed me the hard way that this will not call the appropriate DDX and DDV routines. Instead, we send the IDOK and IDCANCEL commands directly to the property sheet.

The visual glitch

The first version of the code did not consider an inevitable side effect of hiding the tab control: the property sheet doesn't know it's hidden, so it will happily resize the child property page as though the tab were there. What happened was that some dialog real estate was being stolen (the area where the tab control was supposed to be). This was noted by John Simmons (and that is why his name is referenced in the image). Thank you, John!

Solving the glitch involved resizing the active page. This is done in the following method:

// CCeWizard::ResizePage
//
//        Resize the active property page
//
void CCeWizard::ResizePage()
{
    CPropertyPage*    pPage = GetActivePage();

    if(pPage)
    {
        CRect    rc;

        pPage->GetWindowRect(&rc);
        ScreenToClient(&rc);
        rc.bottom += 22;            // MAGIC NUMBER!!!
        pPage->MoveWindow(&rc);
    }
}

This method is called from a number of places in the code, especially from inside UpdateControls().

After testing the code, I found that using the SIP would revert to the old behavior: the lower 22 pixel strip was being stolen again. To solve this CCeWizard must handle the WM_ACTIVATE and WM_SETTINGCHANGE messages. The handlers just call the appropriate shell methods:

// CCeWizard::OnActivate
//
//        Handle the SIP correctly
//
void CCeWizard::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
{
    HWND    hWnd = NULL;

    if(pWndOther)
        hWnd = *pWndOther;

    SHHandleWMActivate(m_hWnd, MAKELPARAM(nState, 
            bMinimized), (LPARAM)hWnd, &m_sai, 0);
}


// CCeWizard::OnSettingChange
//
//        Handle the SIP correctly
//
void CCeWizard::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
    SHHandleWMSettingChange(m_hWnd, 
       (WPARAM)uFlags, (LPARAM)lpszSection, &m_sai);
}

And that's it!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

João Paulo Figueira
Software Developer Frotcom International
Portugal Portugal
I work on R&D for Frotcom International, a company that develops web-based fleet management solutions.
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionHandle to richink window Pinmemberajhuddy28-Jul-06 9:52 
GeneralProblem with Cancel button Pinmemberjmr00713-Jun-05 19:48 
GeneralRe: Problem with Cancel button PinmemberJoão Paulo Figueira13-Jun-05 22:55 
GeneralCompiling and running PinmemberEmma Powell5-Apr-05 10:41 
GeneralRe: Compiling and running PinmemberJoão Paulo Figueira5-Apr-05 11:31 
GeneralRe: Compiling and running PinmemberEmma Powell5-Apr-05 15:36 
GeneralRe: Compiling and running PinmemberJoão Paulo Figueira5-Apr-05 22:42 
QuestionHow to link to an URL PinmemberBui Huy Kien23-Mar-05 18:52 
GeneralExtremely helpful. Pinsussislamomt30-Jan-05 6:56 
GeneralIf real-estate is not too critical.. PinsussRazle14-Nov-04 22:53 
GeneralGreat Idea but... Pinmember37NyyVp20-Mar-04 4:58 
GeneralRe: Great Idea but... PinmemberJoão Paulo Figueira21-Mar-04 12:17 
GeneralOne question ... PinmemberDaniel S.23-Sep-03 23:12 
GeneralRe: One question ... PinmemberJoão Paulo Figueira23-Sep-03 23:19 
GeneralNice Idea ! PinsussSenthil Kumar Umapathy21-Sep-03 21:39 
GeneralRe: Nice Idea ! PinmemberJoão Paulo Figueira21-Sep-03 23:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.141223.1 | Last Updated 20 Sep 2003
Article Copyright 2003 by João Paulo Figueira
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid