Pocket PC 的类似向导的属性表





4.00/5 (8投票s)
使用属性表在 Pocket PC 上实现类似向导的对话框。
引言
众所周知,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_GETTITLE
和 PSCB_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_OK
、ID_BAR_CANCEL
、ID_BAR_BACK
和 ID_BAR_NEXT
。当您创建 CCeWizard
对象时,将工具栏 ID 作为第二个参数传递给构造函数。要显示文本按钮(请参阅下一张图片),请在类构造函数中使用 0 作为第二个参数,并定义以下字符串资源:IDS_BAR_OK
、IDS_BAR_CANCEL
、IDS_BAR_BACK
和 IDS_BAR_NEXT
。
处理导航命令很简单。这是 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()
来终止向导。我的亲身经历告诉我,这样做不会调用适当的 DDX
和 DDV
例程。相反,我们直接将 IDOK
和 IDCANCEL
命令发送到属性表。
视觉故障
代码的第一版没有考虑到隐藏选项卡控件的一个不可避免的副作用:属性表不知道它已隐藏,因此它会愉快地调整子属性页的大小,就好像选项卡存在一样。发生的情况是,一些对话框空间被占用了(选项卡控件本应在的区域)。这是 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_ACTIVATE
和 WM_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); }
就是这样!