65.9K
CodeProject 正在变化。 阅读更多。
Home

Pocket PC 的类似向导的属性表

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (8投票s)

2003 年 9 月 20 日

CPOL

5分钟阅读

viewsIcon

93443

downloadIcon

249

使用属性表在 Pocket PC 上实现类似向导的对话框。

Sample Image - CeWizard.jpg

引言

众所周知,Pocket PC 不支持向导对话框。尽管文档称属性表支持 PSH_WIZARD 标志,但似乎并未按预期工作。

本文介绍了一种使用非常简单的技巧,通过属性表实现类似向导对话框的方法。还有其他关于创建向导对话框的资源,其中最著名的是 Daniel S. 使用对话框的实现:QA: How can I create a wizard style dialog?。本文是我之前发布的文章的更新版本:QA: How can I use a property sheet to implement a Wizard?

在本文的第二版中,解决了 John Simmons 报告的“视觉故障”。

Pocket PC 属性表

在本文这里发表的一篇文章 - 请参阅 Property sheet callbacks in the Pocket PC 2002 - 中介绍了属性表回调机制,并通过 CCePropertySheet 类使其与 MFC 兼容。通过使用自定义的回调函数,可以添加大多数设置属性表中找到的页眉和页脚。页眉和页脚通过处理 PSCB_GETTITLEPSCB_GETLINKTEXT 回调消息来设置。

本文未说明的是系统如何组装属性表,以及如何将此信息用于您自己的目的。属性表是一个包含以下项目的对话框:

  • 属性页对话框
  • 页眉。这是一个可选的静态子控件,其 ID 为 0x3028。
  • 选项卡控件。这是一个子控件,其 ID 为 0x3020。MFC 将其标识为 AFX_IDC_TAB_CONTROL
  • 页脚。这是一个可选的富文本控件,其文本 - 由 PSCB_GETLINKTEXT 设置 - 被解释为 EM_INSERTLINKS 消息的参数(有关更多信息,请参阅richink.h)。

本文的主要思想是,可以隐藏选项卡控件,并且可以使用 SetActivePage()GetActiveIndex()GetPageCount() 来实现类似向导的导航。

实现

现在我们可以实现一个使用属性表(CCeWizard)的类似向导的对话框。此类派生自 CCePropertySheet,从而允许用户插入页眉和页脚。我们需要做的第一件事是设置对话框。

// 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;
}

稍后我们将处理 PopulateToolBar()UpdateControls() 方法。现在,您可能会注意到,在隐藏选项卡控件后,分隔页眉和对话框的线条也消失了。显然,这条线是选项卡控件的一部分,因此它被隐藏了。为了绕过这个问题,我们必须在 OnPaint 方法中自己绘制它。

// 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);
    }
}

请注意,仅当有标题时(m_strTitle 属于 CCePropertySheet 类)才会绘制该线条。

导航

现在,我们必须处理向导的导航:在隐藏选项卡控件后,我们必须提供一种方法让用户翻阅各个页面(属性页)。放置控件按钮的最佳位置是命令栏。CCeWizard 类提供两个选项来将控件放置在命令栏上(尽管您可以绝对覆盖此功能):图形按钮或文本按钮。如果您想提供图形按钮(请参阅顶部图片),请在资源编辑器中创建一个工具栏,其中至少包含四个按钮:ID_BAR_OKID_BAR_CANCELID_BAR_BACKID_BAR_NEXT。当您创建 CCeWizard 对象时,将工具栏 ID 作为第二个参数传递给构造函数。要显示文本按钮(请参阅下一张图片),请在类构造函数中使用 0 作为第二个参数,并定义以下字符串资源:IDS_BAR_OKIDS_BAR_CANCELIDS_BAR_BACKIDS_BAR_NEXT

Wizard with text buttons

处理导航命令很简单。这是 ID_BAR_BACK 处理程序。

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

    UpdateControls();
}

现在是 ID_BAR_NEXT 处理程序。

// CCeWizard::OnBarNext
//
//        Moves to the next page
//
void CCeWizard::OnBarNext() 
{
    SetActivePage(GetActiveIndex() + 1);

    UpdateControls();
}

更新控件

现在,让我们看看如何更新向导的控件。此任务是必要的,以便让应用程序用户了解他们在向导中的位置。这有两种方式:更新导航按钮并在向导的页眉中报告进度。所有这些都在一个方法中实现。

// 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();
}

请注意,无论导航按钮是图形还是文本,其状态的更新方式相同。

插入工具栏

图形和文本工具栏都使用同一个方法插入。

// 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);
    }
}

终止向导

不应使用直接调用 EndDialog() 来终止向导。我的亲身经历告诉我,这样做不会调用适当的 DDXDDV 例程。相反,我们直接将 IDOKIDCANCEL 命令发送到属性表。

视觉故障

代码的第一版没有考虑到隐藏选项卡控件的一个不可避免的副作用:属性表不知道它已隐藏,因此它会愉快地调整子属性页的大小,就好像选项卡存在一样。发生的情况是,一些对话框空间被占用了(选项卡控件本应在的区域)。这是 John Simmons 注意到的(这也是为什么他在图片中引用了他的名字)。谢谢你,John!

解决此故障涉及调整活动页面的大小。这是在以下方法中完成的。

// 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);
    }
}

此方法从代码中的多个位置调用,尤其是在 UpdateControls() 内部。

在测试代码后,我发现使用 SIP 会恢复到旧行为:较低的 22 像素条又被占用了。为了解决这个问题,CCeWizard 必须处理 WM_ACTIVATEWM_SETTINGCHANGE 消息。处理程序只是调用适当的 shell 方法。

// 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);
}

就是这样!

© . All rights reserved.