Pocket PC操作系统并不支持向导对话框,尽管文档中提到属性页支持PSH_WIZARD标志,但实际上它并不像预期的那样工作。本文将介绍一种通过属性页实现类似向导对话框的简单方法。
在之前的文章中,介绍了属性页回调机制,并使其与MFC兼容,通过CCePropertySheet类。通过自定义回调函数,可以在大多数设置属性页中添加标题和页脚。本文将探讨属性页是如何由系统组装的,以及如何利用这些信息来实现自己的向导式对话框。
属性页是一个包含以下项目的对话框:
本文的主要思想是隐藏标签控件,并通过SetActivePage()、GetActiveIndex()和GetPageCount()实现类似向导的导航。
现在,准备使用属性页(CCeWizard)实现一个类似向导的对话框。这个类派生自CCePropertySheet,允许用户插入标题和页脚。首先,需要设置对话框:
BOOL CCeWizard::OnInitDialog() {
BOOL bResult = CCePropertySheet::OnInitDialog();
HWND hWndTab;
hWndTab = ::GetDlgItem(m_hWnd, AFX_IDC_TAB_CONTROL);
if (hWndTab) ::ShowWindow(hWndTab, SW_HIDE);
ModifyStyle(0, WS_NONAVDONEBUTTON, SWP_NOSIZE);
SHDoneButton(m_hWnd, SHDB_HIDE);
PopulateToolBar();
UpdateControls();
return bResult;
}
稍后将讨论PopulateToolBar()和UpdateControls()方法。现在,可能会注意到,在隐藏标签控件后,将标题与对话框分隔的线也消失了。显然,这条线是标签控件的一部分,因此它也被隐藏了。为了解决这个问题,必须自己在OnPaint方法中绘制它:
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处理程序:
void CCeWizard::OnBarBack() {
SetActivePage(GetActiveIndex() - 1);
UpdateControls();
}
现在是ID_BAR_NEXT处理程序:
void CCeWizard::OnBarNext() {
SetActivePage(GetActiveIndex() + 1);
UpdateControls();
}
现在,让看看如何更新向导的控件。这项任务是必要的,以便让用户知道他们在向导中的位置。这可以通过两种方式完成:更新导航按钮和在向导的标题中报告进度。所有这些只需一个方法即可实现:
void CCeWizard::UpdateControls() {
int iIndex = GetActiveIndex(), nPages = GetPageCount();
CToolBarCtrl& rToolBar = m_pWndEmptyCB->GetToolBarCtrl();
CWnd* pWndHdr;
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);
}
rToolBar.EnableButton(ID_BAR_BACK, iIndex > 0);
rToolBar.EnableButton(ID_BAR_NEXT, iIndex < nPages - 1);
ResizePage();
}
请注意,无论导航按钮是图形还是文本,更新其状态的方式都是相同的。
图形和文本工具栏都使用一个方法插入:
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!
解决这个错误涉及到调整活动页面的大小。这是在以下方法中完成的:
void CCeWizard::ResizePage() {
CPropertyPage* pPage = GetActivePage();
if (pPage) {
CRect rc;
pPage->GetWindowRect(&rc);
ScreenToClient(&rc);
rc.bottom += 22;
pPage->MoveWindow(&rc);
}
}
此方法在代码中的多个地方被调用,特别是在UpdateControls()内部。
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);
}
void CCeWizard::OnSettingChange(UINT uFlags, LPCTSTR lpszSection) {
SHHandleWMSettingChange(m_hWnd, (WPARAM)uFlags, (LPARAM)lpszSection, &m_sai);
}