📄 wtl for mfc programmers, part viii.mht
字号:
<SPAN class=3Dcpp-comment>// Maps</SPAN>
BEGIN_MSG_MAP(COptionsWizard)
CHAIN_MSG_MAP(CPropertySheetImpl<COptionsWizard>)
END_MSG_MAP()
<SPAN class=3Dcpp-comment>// Property pages</SPAN>
CWizIntroPage m_pgIntro;
};
COptionsWizard::COptionsWizard ( HWND hWndParent ) :
CPropertySheetImpl<COptionsWizard> ( 0U, <SPAN =
class=3Dcpp-literal>0</SPAN>, hWndParent )
{
SetWizardMode();
AddPage ( m_pgIntro );
}</PRE>
<P>Then the <CODE>CMainFrame</CODE> handler for the=20
<I>Tools</I>|<I>Wizard</I> menu looks like this:</P><PRE><SPAN =
class=3Dcpp-keyword>void</SPAN> CMainFrame::OnOptionsWizard ( UINT =
uCode, <SPAN class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl )
{
COptionsWizard wizard;
wizard.DoModal();
}</PRE>
<P>And here's the wizard in action:</P>
<P><IMG height=3D348 alt=3D" [Wizard on intro page - 6K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/wizintro.png" =
width=3D441=20
align=3Dbottom border=3D0></P>
<H3><A name=3Dmorepages></A>Adding More Pages, Handling DDV</H3>
<P>To make this a useful wizard, we'll add a new page for setting =
the=20
view's background color. This page will also have a checkbox for=20
demonstrating handling a DDV failure and preventing the user from=20
continuing on with the wizard. Here's the new page, whose ID is=20
<CODE>IDD_WIZARD_BKCOLOR</CODE>:</P>
<P><IMG height=3D304 alt=3D" [Color selection wizard page - 4K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/colorpage.png" =
width=3D348=20
align=3Dbottom border=3D0></P>
<P>The implementation for this page is in the class=20
<CODE>CWizBkColorPage</CODE>. Here are the parts relating =
to</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CWizBkColorPage :
<SPAN class=3Dcpp-keyword>public</SPAN> =
CPropertyPageImpl<CWizBkColorPage>,
<SPAN class=3Dcpp-keyword>public</SPAN> =
CWinDataExchange<CWizBkColorPage>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-comment>// some stuff removed for =
brevity...</SPAN>
BEGIN_DDX_MAP(CWizBkColorPage)
DDX_RADIO(IDC_BLUE, m_nColor)
DDX_CHECK(IDC_FAIL_DDV, m_bFailDDV)
END_DDX_MAP()
<SPAN class=3Dcpp-comment>// Notification handlers</SPAN>
<SPAN class=3Dcpp-keyword>int</SPAN> OnSetActive();
BOOL OnKillActive();
<SPAN class=3Dcpp-comment>// DDX vars</SPAN>
<SPAN class=3Dcpp-keyword>int</SPAN> m_nColor;
<SPAN class=3Dcpp-keyword>protected</SPAN>:
<SPAN class=3Dcpp-keyword>int</SPAN> m_bFailDDV;
};</PRE>
<P><CODE>OnSetActive()</CODE> works similarly to the intro page, =
it=20
enables both the <I>Back</I> and <I>Next</I> buttons.=20
<CODE>OnKillActive()</CODE> is a new handler, it invokes DDX then =
checks=20
the value of <CODE>m_bFailDDV</CODE>. If that is TRUE, meaning the =
checkbox was checked, <CODE>OnKillActive()</CODE> prevents the =
wizard from=20
going to the next page.</P><PRE><SPAN =
class=3Dcpp-keyword>int</SPAN> CWizBkColorPage::OnSetActive()
{
SetWizardButtons ( PSWIZB_BACK | PSWIZB_NEXT );
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
<SPAN class=3Dcpp-keyword>int</SPAN> CWizBkColorPage::OnKillActive()
{
<SPAN class=3Dcpp-keyword>if</SPAN> ( !DoDataExchange(<SPAN =
class=3Dcpp-keyword>true</SPAN>) )
<SPAN class=3Dcpp-keyword>return</SPAN> TRUE; <SPAN =
class=3Dcpp-comment>// prevent deactivation</SPAN>
<SPAN class=3Dcpp-keyword>if</SPAN> ( m_bFailDDV )
{
MessageBox (
_T(<SPAN class=3Dcpp-string>"Error box checked, wizard will =
stay on this page."</SPAN>),
_T(<SPAN class=3Dcpp-string>"PSheets"</SPAN>), MB_ICONERROR );
<SPAN class=3Dcpp-keyword>return</SPAN> TRUE; <SPAN =
class=3Dcpp-comment>// prevent deactivation</SPAN>
}
<SPAN class=3Dcpp-keyword>return</SPAN> FALSE; <SPAN =
class=3Dcpp-comment>// allow deactivation</SPAN>
}</PRE>
<P>Note that the logic in <CODE>OnKillActive()</CODE> could =
certainly have=20
been put in <CODE>OnWizardNext()</CODE> instead, since both =
handlers have=20
the ability to keep the wizard on the current page. The difference =
is that=20
<CODE>OnKillActive()</CODE> is called when the user clicks =
<I>Back</I> or=20
<I>Next</I>, while <CODE>OnWizardNext()</CODE> is only called when =
the=20
user clicks <I>Next</I> (as the name signifies).=20
<CODE>OnWizardNext()</CODE> is also used for other purposes; it =
can direct=20
the wizard to a different page instead of the next one in order, =
if some=20
pages need to be skipped.</P>
<P>The wizard in the sample project has two more pages,=20
<CODE>CWizBkPicturePage</CODE> and <CODE>CWizFinishPage</CODE>. =
Since they=20
are similar to the two pages above, I won't cover them in detail =
here, but=20
you can check out the sample code to get all the details.</P>
<H2><A name=3Dotherui></A>Other UI Considerations</H2>
<H3><A name=3Dcentersheet></A>Centering a sheet</H3>
<P>The default behavior of sheets and wizards is to appear near =
the=20
upper-left corner of their parent window:</P>
<P><IMG height=3D326 alt=3D" [sheet position - 8K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/sheetpos.png" =
width=3D410=20
align=3Dbottom border=3D0></P>
<P>This looks rather sloppy, but fortunately we can remedy it. The =
first=20
approach I thought of was to override=20
<CODE>CPropertySheetImpl::PropSheetCallback()</CODE> and center =
the sheet=20
in that function. <CODE>PropSheetCallback()</CODE> is a callback =
function=20
described in MSDN as <CODE>PropSheetProc()</CODE>. The OS calls =
this=20
function when the sheet is being created, and WTL uses this time =
to=20
subclass the sheet window. So our first attempt might =
be:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CAppPropertySheet : =
<SPAN class=3Dcpp-keyword>public</SPAN> =
CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=3Dcpp-comment>//...</SPAN>
<SPAN class=3Dcpp-keyword>static</SPAN> <SPAN =
class=3Dcpp-keyword>int</SPAN> CALLBACK PropSheetCallback(HWND hWnd, =
UINT uMsg, LPARAM lParam)
{
<SPAN class=3Dcpp-keyword>int</SPAN> nRet =3D =
CPropertySheetImpl<CAppPropertySheet>::PropSheetCallback (
hWnd, uMsg, =
lParam );
=20
<SPAN class=3Dcpp-keyword>if</SPAN> ( PSCB_INITIALIZED =3D=3D =
uMsg )
{
<SPAN class=3Dcpp-comment>// center sheet... somehow?</SPAN>
}
=20
<SPAN class=3Dcpp-keyword>return</SPAN> nRet;
}
};</PRE>
<P>As you can see, we're stuck. <CODE>PropSheetCallback()</CODE> =
is a=20
static method, so we have no <CODE><SPAN=20
class=3Dcpp-keyword>this</SPAN></CODE> pointer to access the sheet =
window=20
with. OK, so how about copying the code from=20
<CODE>CPropertySheetImpl::PropSheetCallback()</CODE> and then =
adding our=20
own? Putting aside for the moment how this ties our code to this=20
particular version of WTL (which should already be a tip-off that =
this=20
isn't a good approach), the code would be:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CAppPropertySheet : <SPAN =
class=3Dcpp-keyword>public</SPAN> =
CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=3Dcpp-comment>//...</SPAN>
<SPAN class=3Dcpp-keyword>static</SPAN> <SPAN =
class=3Dcpp-keyword>int</SPAN> CALLBACK PropSheetCallback(HWND hWnd, =
UINT uMsg, LPARAM)
{
<SPAN class=3Dcpp-keyword>if</SPAN>(uMsg =3D=3D =
PSCB_INITIALIZED)
{
<B><SPAN class=3Dcpp-comment>// Code copied from WTL and =
tweaked to use CAppPropertySheet</SPAN>
<SPAN class=3Dcpp-comment>// instead of T:</B></SPAN>
ATLASSERT(hWnd !=3D NULL);
CAppPropertySheet* pT =3D (CAppPropertySheet*)
_Module.ExtractCreateWndData();
<SPAN class=3Dcpp-comment>// subclass the sheet =
window</SPAN>
pT->SubclassWindow(hWnd);
<SPAN class=3Dcpp-comment>// remove page handles =
array</SPAN>
pT->_CleanUpPages();
=20
<B><SPAN class=3Dcpp-comment>// Our own code follows:</SPAN>
pT->CenterWindow ( pT->m_psh.hwndParent );</B>
}
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}
};</PRE>
<P>This looks good in theory, but when I tried it, the sheet's =
position=20
did not change. Apparently, the common controls code repositions =
the sheet=20
again after our call to <CODE>CenterWindow()</CODE>.</P>
<P>So, having given up on a nice solution that's entirely =
encapsulated in=20
the sheet class, I fell back on having the sheet and the pages =
cooperate=20
to center the sheet. I added a user-defined message called=20
<CODE>UWM_CENTER_SHEET</CODE>:</P><PRE><SPAN =
class=3Dcpp-preprocessor> #define UWM_CENTER_SHEET WM_APP</SPAN></PRE>
<P><CODE>CAppPropertySheet</CODE> handles this message in its =
message=20
map:</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> =
CAppPropertySheet : <SPAN class=3Dcpp-keyword>public</SPAN> =
CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=3Dcpp-comment>//...</SPAN>
BEGIN_MSG_MAP(CAppPropertySheet)
MESSAGE_HANDLER_EX(UWM_CENTER_SHEET, OnPageInit)
CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
END_MSG_MAP()
=20
<SPAN class=3Dcpp-comment>// Message handlers</SPAN>
LRESULT OnPageInit ( UINT, WPARAM, LPARAM );
=20
<SPAN class=3Dcpp-keyword>protected</SPAN>:
<SPAN class=3Dcpp-keyword>bool</SPAN> m_bCentered; <SPAN =
class=3Dcpp-comment>// set to false in the ctor</SPAN>
};
=20
LRESULT CAppPropertySheet::OnPageInit ( UINT, WPARAM, LPARAM )
{
<SPAN class=3Dcpp-keyword>if</SPAN> ( !m_bCentered )
{
m_bCentered =3D <SPAN class=3Dcpp-keyword>true</SPAN>;
CenterWindow ( m_psh.hwndParent );
}
=20
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>Then, the <CODE>OnInitDialog()</CODE> method of each page sends =
this=20
message to the sheet:</P><PRE>BOOL =
CBackgroundOptsPage::OnInitDialog ( HWND hwndFocus, LPARAM lParam )
{
<B>GetPropertySheet().SendMessage ( UWM_CENTER_SHEET );</B>
=20
DoDataExchange(<SPAN class=3Dcpp-keyword>false</SPAN>);
<SPAN class=3Dcpp-keyword>return</SPAN> TRUE;
}</PRE>
<P>The <CODE>m_bCentered</CODE> flag in the sheet ensures that =
only the=20
first <CODE>UWM_CENTER_SHEET</CODE> message is acted on.</P>
<H3><A name=3Daddicons></A>Adding icons to pages</H3>
<P>To use other features of sheets and pages that are not already =
wrapped=20
by member functions, you'll need to access the relevant structures =
directly: the <CODE>PROPSHEETHEADER</CODE> member =
<CODE>m_psh</CODE> in=20
<CODE>CPropertySheetImpl</CODE>, or the <CODE>PROPSHEETPAGE</CODE> =
member=20
<CODE>m_psp</CODE> in <CODE>CPropertyPageImpl</CODE>.</P>
<P>For example, to add an icon to the Background page of the =
options=20
property sheet, we need to add a flag and set a couple other =
members in=20
the page's <CODE>PROPSHEETPAGE</CODE> =
struct:</P><PRE>CBackgroundOptsPage::CBackgroundOptsPage()
{
m_psp.dwFlags |=3D PSP_USEICONID;
m_psp.pszIcon =3D MAKEINTRESOURCE(IDI_TABICON);
m_psp.hInstance =3D _Module.GetResourceInstance();
}</PRE>
<P>And here's the result:</P>
<P><IMG height=3D67 alt=3D" [Tab icon - 2K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/tabicon.png" =
width=3D163=20
align=3Dbottom border=3D0></P>
<H2><A name=3Dupnext></A>Up Next</H2>
<P>In Part 9, I'll cover WTL's utility classes, and its wrappers =
for GDI=20
object and common dialogs.=20
<H2><A name=3Drevisionhistory></A>Revision History</H2>
<P>September 13, 2003: Article first published.=20
<!-- Article Ends --></P></DIV>
<H2>About Michael Dunn</H2>
<TABLE width=3D"100%" border=3D0>
<TBODY>
<TR vAlign=3Dtop>
<TD class=3DsmallText noWrap><IMG=20
=
src=3D"http://www.codeproj
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -