📄 wtl for mfc programmers, part viii.mht
字号:
ever</H3>
<P>After making an SDI project with the WTL AppWizard, we can =
start by=20
creating a sheet to use for our About box. Let's start with the =
about=20
dialog the wizard creates for us, and change the styles so it will =
work as=20
a property page.</P>
<P>The first step is to remove the OK button, since that doesn't =
make=20
sense in a sheet. On the <I>Styles</I> tab, change the =
<I>Style</I> to=20
<I>Child</I>, and the <I>Border</I> to <I>Thin</I>, while leaving =
<I>Title=20
bar</I> checked. On the <I>More Styles</I> tab, check =
<I>Disabled</I>.</P>
<P>The second (and final) step is to create a property sheet in =
the=20
<CODE>OnAppAbout()</CODE> handler. We can do this with the=20
non-customizable <CODE>CPropertySheet</CODE> and=20
<CODE>CPropertyPage</CODE> classes:</P><PRE>LRESULT =
CMainFrame::OnAppAbout(...)
{
CPropertySheet sheet ( _T(<SPAN class=3Dcpp-string>"About =
PSheets"</SPAN>) );
CPropertyPage<IDD_ABOUTBOX> pgAbout;
=20
sheet.AddPage ( pgAbout );
sheet.DoModal();
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>The result looks like this:</P>
<P><IMG height=3D282 alt=3D" [Simple sheet - 5K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/simplesheet.png" =
width=3D344=20
align=3Dbottom border=3D0></P>
<H3><A name=3Dusefulpage></A>Creating a useful property page</H3>
<P>Since not every page in every sheet is as simple as an About =
box, most=20
pages will require a <CODE>CPropertyPageImpl</CODE>-derived class, =
so=20
we'll take a look at such a class now. We'll make a new property =
page that=20
contains the settings for the graphics shown in the background of =
the=20
client area. Here's the dialog:</P>
<P><IMG height=3D190 alt=3D" [Background options - 4K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/bkgndsheet.png" =
width=3D243=20
align=3Dbottom border=3D0></P>
<P>This dialog has the same styles as the About page. We'll need a =
new=20
class to go along with the page, called =
<CODE>CBackgroundOptsPage</CODE>.=20
This class derives from <CODE>CPropertyPageImpl</CODE> since it's =
a=20
property page, and <CODE>CWinDataExchange</CODE> to enable =
DDX.</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> CBackgroundOptsPage =
:
<SPAN class=3Dcpp-keyword>public</SPAN> =
CPropertyPageImpl<CBackgroundOptsPage>,
<SPAN class=3Dcpp-keyword>public</SPAN> =
CWinDataExchange<CBackgroundOptsPage>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-keyword>enum</SPAN> { IDD =3D IDD_BACKGROUND_OPTS =
};
=20
<SPAN class=3Dcpp-comment>// Construction</SPAN>
CBackgroundOptsPage();
~CBackgroundOptsPage();
=20
<SPAN class=3Dcpp-comment>// Maps</SPAN>
BEGIN_MSG_MAP(CBackgroundOptsPage)
MSG_WM_INITDIALOG(OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl<CBackgroundOptsPage>)
END_MSG_MAP()
=20
BEGIN_DDX_MAP(CBackgroundOptsPage)
DDX_RADIO(IDC_BLUE, m_nColor)
DDX_RADIO(IDC_ALYSON, m_nPicture)
END_DDX_MAP()
=20
<SPAN class=3Dcpp-comment>// Message handlers</SPAN>
BOOL OnInitDialog ( HWND hwndFocus, LPARAM lParam );
=20
<SPAN class=3Dcpp-comment>// Property page notification =
handlers</SPAN>
<SPAN class=3Dcpp-keyword>int</SPAN> OnApply();
=20
<SPAN class=3Dcpp-comment>// DDX variables</SPAN>
<SPAN class=3Dcpp-keyword>int</SPAN> m_nColor, m_nPicture;
};</PRE>
<P>Things to note in this class:=20
<UL>
<LI>There is a public member named <CODE>IDD</CODE> that holds =
the=20
associated dialog resource ID.=20
<LI>The message map resembles that of a <CODE>CDialogImpl</CODE> =
class.=20
<LI>The message map chains messages to =
<CODE>CPropertyPageImpl</CODE> so=20
that property sheet-related notification messages are handled.=20
<LI>There is an <CODE>OnApply()</CODE> handler to save the =
user's=20
choices when he clicks OK in the sheet. </LI></UL>
<P><CODE>OnApply()</CODE> is pretty simple, it calls=20
<CODE>DoDataExchange()</CODE> to update the DDX variables, then =
returns a=20
code indicating whether the sheet should close or =
not:</P><PRE><SPAN class=3Dcpp-keyword>int</SPAN> =
CBackgroundOptsPage::OnApply()
{
<SPAN class=3Dcpp-keyword>return</SPAN> DoDataExchange(<SPAN =
class=3Dcpp-keyword>true</SPAN>) ? PSNRET_NOERROR : PSNRET_INVALID;
}</PRE>
<P>In the main frame, we'll add a <I>Tools|Options</I> menu item =
that=20
brings up the property sheet. The handler for this command creates =
the=20
property sheet as before, but with the new=20
<CODE>CBackgroundOptsPage</CODE> added to the sheet.</P><PRE><SPAN =
class=3Dcpp-keyword>void</SPAN> CMainFrame::OnOptions ( UINT uCode, =
<SPAN class=3Dcpp-keyword>int</SPAN> nID, HWND hwndCtrl )
{
CPropertySheet sheet ( _T(<SPAN class=3Dcpp-string>"PSheets =
Options"</SPAN>), <SPAN class=3Dcpp-literal>0</SPAN> );
CBackgroundOptsPage pgBackground;
CPropertyPage<IDD_ABOUTBOX> pgAbout;
=20
pgBackground.m_nColor =3D m_view.m_nColor;
pgBackground.m_nPicture =3D m_view.m_nPicture;
=20
sheet.m_psh.dwFlags |=3D PSH_NOAPPLYNOW;
=20
sheet.AddPage ( pgBackground );
sheet.AddPage ( pgAbout );
=20
<SPAN class=3Dcpp-keyword>if</SPAN> ( IDOK =3D=3D sheet.DoModal() )
m_view.SetBackgroundOptions ( pgBackground.m_nColor,
pgBackground.m_nPicture );
}</PRE>
<P>The <CODE>sheet</CODE> constructor now has a second parameter =
of 0,=20
meaning that the page at index 0 should be visible initially. You =
could=20
change that to 1 to have the About page be visible when the sheet =
first=20
appears. Since this is just demo code, I'm going to be lazy and =
make the=20
<CODE>CBackgroundOptsPage</CODE> variables connected to the radio =
buttons=20
public. The main frame will just store the initial values in those =
variables, and read them again if the user clicks OK in the =
sheet.</P>
<P>If the user clicks OK, <CODE>DoModal()</CODE> returns=20
<CODE>IDOK</CODE>, and we tell the view the new picture and color =
to use.=20
Here are some screen shots to show the different views:</P>
<P><A =
href=3D"http://www.codeproject.com/wtl/WTL4MFC8/app_aly.jpg"><IMG=20
height=3D152 alt=3D" [Alyson background - 11K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/app_aly_t.png" =
width=3D131=20
align=3Dbottom border=3D2></A> <A=20
href=3D"http://www.codeproject.com/wtl/WTL4MFC8/sb_app.png"><IMG =
height=3D133=20
alt=3D" [Strong Bad background - 9K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/sb_app_t.png" =
width=3D95=20
align=3Dbottom border=3D2></A></P>
<H3><A name=3Dbettersheet></A>Creating a better property sheet =
class</H3>
<P>The <CODE>OnOptions()</CODE> handler creates the sheet just =
fine, but=20
there's an awful lot of setup and initialization code there, which =
really=20
shouldn't be <CODE>CMainFrame</CODE>'s responsibility. A better =
way is to=20
make a class derived from <CODE>CPropertySheetImpl</CODE> that =
handles=20
those tasks.</P><PRE><SPAN class=3Dcpp-preprocessor>#include =
"BackgroundOptsPage.h"</SPAN>
=20
<SPAN class=3Dcpp-keyword>class</SPAN> CAppPropertySheet : <SPAN =
class=3Dcpp-keyword>public</SPAN> =
CPropertySheetImpl<CAppPropertySheet>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-comment>// Construction</SPAN>
CAppPropertySheet ( _U_STRINGorID title =3D (LPCTSTR) NULL,=20
UINT uStartPage =3D <SPAN =
class=3Dcpp-literal>0</SPAN>, HWND hWndParent =3D NULL );
<SPAN class=3Dcpp-comment>// Maps</SPAN>
BEGIN_MSG_MAP(CAppPropertySheet)
CHAIN_MSG_MAP(CPropertySheetImpl<CAppPropertySheet>)
END_MSG_MAP()
<SPAN class=3Dcpp-comment>// Property pages</SPAN>
CBackgroundOptsPage m_pgBackground;
CPropertyPage<IDD_ABOUTBOX> m_pgAbout;
};</PRE>
<P>With this class, we've encapsulated the details of what pages =
are in=20
the sheet, and moved them into the sheet class itself. The =
constructor=20
handles adding the pages to the sheet, and setting any other =
necessary=20
flags:</P><PRE>CAppPropertySheet::CAppPropertySheet ( =
_U_STRINGorID title, UINT uStartPage,=20
HWND hWndParent ) :
CPropertySheetImpl<CAppPropertySheet> ( title, uStartPage, =
hWndParent )
{
m_psh.dwFlags |=3D PSH_NOAPPLYNOW;
AddPage ( m_pgBackground );
AddPage ( m_pgAbout );
}</PRE>
<P>As a result, the <CODE>OnOptions()</CODE> handler becomes a bit =
simpler:</P><PRE><SPAN class=3Dcpp-keyword>void</SPAN> =
CMainFrame::OnOptions ( UINT uCode, <SPAN class=3Dcpp-keyword>int</SPAN> =
nID, HWND hwndCtrl )
{
CAppPropertySheet sheet ( _T(<SPAN class=3Dcpp-string>"PSheets =
Options"</SPAN>), <SPAN class=3Dcpp-literal>0</SPAN> );
sheet.m_pgBackground.m_nColor =3D m_view.m_nColor;
sheet.m_pgBackground.m_nPicture =3D m_view.m_nPicture;
<SPAN class=3Dcpp-keyword>if</SPAN> ( IDOK =3D=3D sheet.DoModal() )
m_view.SetBackgroundOptions ( sheet.m_pgBackground.m_nColor,
sheet.m_pgBackground.m_nPicture );
}</PRE>
<H2><A name=3Dcreatewiz></A>Creating a Wizard</H2>
<P>Creating a wizard is, not surprisingly, similar to creating a =
property=20
sheet. A little more work is required to enable the <I>Back</I> =
and=20
<I>Next</I> buttons; just as in MFC property pages, you override=20
<CODE>OnSetActive()</CODE> and call =
<CODE>SetWizardButtons()</CODE> to=20
enable the appropriate buttons. We'll start with a simple =
introduction=20
page, with ID <CODE>IDD_WIZARD_INTRO</CODE>:</P>
<P><IMG height=3D307 alt=3D" [Intro page - 3K] "=20
src=3D"http://www.codeproject.com/wtl/WTL4MFC8/intropage.png" =
width=3D351=20
align=3Dbottom border=3D0></P>
<P>Notice that the page has no caption text. Since every page in a =
wizard=20
usually has the same title, I prefer to set the text in the=20
CPropertySheetImpl constructor, and have every page use the same =
string=20
resource. That way, I can just change that one string and every =
page will=20
reflect the change.</P>
<P>The implementation of this page is done in the=20
<CODE>CWizIntroPage</CODE> class:</P><PRE><SPAN =
class=3Dcpp-keyword>class</SPAN> CWizIntroPage : <SPAN =
class=3Dcpp-keyword>public</SPAN> CPropertyPageImpl<CWizIntroPage>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-keyword>enum</SPAN> { IDD =3D IDD_WIZARD_INTRO };
<SPAN class=3Dcpp-comment>// Construction</SPAN>
CWizIntroPage();
<SPAN class=3Dcpp-comment>// Maps</SPAN>
BEGIN_MSG_MAP(COptionsWizard)
CHAIN_MSG_MAP(CPropertyPageImpl<CWizIntroPage>)
END_MSG_MAP()
<SPAN class=3Dcpp-comment>// Notification handlers</SPAN>
<SPAN class=3Dcpp-keyword>int</SPAN> OnSetActive();
};</PRE>
<P>The constructor sets the page's text by referencing a string =
resource=20
ID:</P><PRE>CWizIntroPage::CWizIntroPage() :
CPropertyPageImpl<CWizIntroPage>( IDS_WIZARD_TITLE )
{
}</PRE>
<P>The string <CODE>IDS_WIZARD_TITLE</CODE> ("PSheets Options =
Wizard")=20
will appear in the wizard's caption bar when this page is the =
current=20
page. <CODE>OnSetActive()</CODE> enables just the <I>Next</I> =
button:</P><PRE><SPAN class=3Dcpp-keyword>int</SPAN> =
CWizIntroPage::OnSetActive()
{
SetWizardButtons ( PSWIZB_NEXT );
<SPAN class=3Dcpp-keyword>return</SPAN> <SPAN =
class=3Dcpp-literal>0</SPAN>;
}</PRE>
<P>To implement the wizard, we'll create a class=20
<CODE>COptionsWizard</CODE>, and a <I>Tools</I>|<I>Wizard</I> menu =
option=20
in the main frame's menu. The <CODE>COptionsWizard</CODE> =
constructor is=20
pretty similar to <CODE>CAppPropertySheet</CODE>'s, in that it =
sets any=20
necessary style bits or flags, and adds pages to the =
sheet.</P><PRE><SPAN class=3Dcpp-keyword>class</SPAN> COptionsWizard : =
<SPAN class=3Dcpp-keyword>public</SPAN> =
CPropertySheetImpl<COptionsWizard>
{
<SPAN class=3Dcpp-keyword>public</SPAN>:
<SPAN class=3Dcpp-comment>// Construction</SPAN>
COptionsWizard ( HWND hWndParent =3D NULL );
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -