MFC programmer's WTL Guide: Part Viii - Property Page and Wizard
Original: Michael Dunn [English] Translation: Orbit (Orange skin dried) [www.winmsg.com]
Download Demonstration Code
This chapter
Describes the attribute form class of WTL
CPROPERTYSHEETIMPL method WTL's property page class
CPROPERTYPAGEWINDOW Method CPROPERTYPAGEIMPL method Processing Notification Messages Create a property sheet
The simplest property table Create a useful Properties page Creating a better properties table class to create a property sheet for the wizard
Add more properties pages, use DDV other interfaces
Set a property sheet to add icons to the properties page Continue to modify the record
Introduction
Using the property sheet in the use of the properties, use the properties table to indicate that some options have become a popular way. The attribute table of the wizard mode is usually used to guide the user to install the software or complete other complex work. WTL provides good support for the attributes of these two ways, which can use the features related to the dialog, such as DDX and DDV. In this chapter I will demonstrate how to create a basic property table and wizard, how to handle the notification messages and events sent by the property page.
WTL properties form
Implementing a property table requires CPropertySheetWindow and CPropertySheetImpl two classes, they are defined in the atldlgs.h header file. The CPropertySheetWindow class is a window interface class (which means a CWindow derived class), CPropertySheetImpl has a full implementation of message mapping chains and windows, which is similar to the basic window classes of ATL, which requires Cwindow and CWindowIMPL two classes.
The CPROPERTYSHEETWINDOW class encapsulates the processing of various PSM_ * messages, for example, setActivePagebyId () encapsulates the PSM_setcurseelid message. CPROPERTYSHEETIMPL class Manages a popsheetHeader structure and an array of HPropSheetPage types, and the CPropertySheetImpl class also provides some ways to populate the PropsheetHeader structure, add or delete the property page, you can also use the M_PSH member variable to directly operate the PropsheetHeader structure.
Finally, the CPropertySheet class is a special case for the CPROPERTYSHEETIMPL class, which you can use directly without customizing the entire property sheet.
CPROPERTYSHEETIMPL method
Below is some important ways to CPROPERTYSHEETIMPL classes. Since many methods are just a package of window messages, they are not listed here, you can view the full function list in Atldlgs.h.
CpropertySheetImpl (_U_Stringorid Title = (LPCTSTR) NULL,
Uint uStartPage = 0, hwnd hwndparent = null)
The constructor of the CPROPERTYSHEETIMPL class allows you to use some common properties (default), so you don't need to set them in calling other methods. Title Specifies the text of the title bar displayed in the property table, _u_stringorid is a WTL tool class, which can automatically convert LPCTSTR and resource IDs, for example, the following two lines of code are correct:
CPropertySheetImpl MySheet (IDS_SHEET_TITLE);
CPropertySheetImpl MySheet ("My Prop Sheet");
IDS_SHEET_TITLE is the id of the string. USTARTPAGE is the property page activated when the property is started, which is an index starting from 0. HWndParent is the handle of the parent window of the property table. Bool Addpage (HPropsheetPage Hpage) Bool Addpage (LPCPropsheetPage PPAGE)
Add a property page. If this property page has been created, you can use the first overload function, use the HPropsheetPage of the Properties page as a parameter. Usually use the second overload function, using this overload function Simply set a PropsheetPage data structure (later, it works with CPROPERTYPAGEIMPL), CPropertySheetImpl will create and manage this property page for you and manage this property page.
Bool Removepage (HPropsheetPage Hpage) Bool Removepage (Int NpageIndex)
Remove a property page, you can use the handle or index of the property page.
Bool SetActivePage (HPropsheetPage Hpage) Bool SetActivePage (Int NpageIndex)
Set the activity page for the property table. You can use the handle or index of the properties page. You can use this method dynamic settings that use this method to dynamically set the activated property page before you create (displays).
Void settitle (lpctstr lpsztext, uint nstyle = 0)
The title text of the property table window. NStyle can be 0 or psh_proptitle, if it is psh_proptitle, the attribute table has a psh_proptitle style so that the system adds a string "Properties for" before you specify by the LPSZText parameter.
Void setWizardMode ()
Set the PSH_Wizard style, rename the attribute table to the wizard mode, which must be called before the property table display.
void enablehelp ()
Set the PSH_HASHELP style, add the help button to the property table. It should be noted that you also want to make the help buttons available and help in each property page.
INT_PTR DOMODAL (HWND HWNDPARENT = :: getActiveWindow ())
Create and display a mode list, the return positive value indicates that the operation is successful, and the help document for the PropertySheet () API has a detailed explanation of the return value. If an error occurs, the property table cannot be created, and Domodal () returns -1.
HWND CREATE (HWND HWNDPARENT = NULL)
Create and display a modeless property sheet, the return value is the handle of the window, if an error occurs, the property table cannot be created, and create () returns NULL.
WTL's property page class
WTL is similar to the package class of the property page with the package class of the property sheet, with a window interface class CPropertyPageWindow and an implementation class CPropertyPageImpl. CPropertyPageWindow is small, including the most common way to be called in the attribute table as the parent window.
CPropertyPageImpL is derived from CDialogIMPLBaset, because the property page is created from the dialog resource, which means that all the WTLs that can be used in the dialog can be used in the property page, such as DDX and DDV. CPRopertyPageImpl has two main roles: managing a PropSheetPage data structure (saved in member variable m_psp) to process all PSN_ notification messages. For a very simple property page, you can use the CPROPERTYPAGE class directly. This class is only suitable for the property page without any interaction, such as the "About" page or the introduction page in the wizard, you can also create the properties page with the ActiveX control. First, this needs to be added to Atlhost.h in the stdafx.h file, but also use CAXPROPERTYPAGEIMPL instead of CPROPERTYPAGEIMPL. For simple pages, you can use CaxPropertyPage instead of CPropertyPage.
CPROPERTYPAGEIMPL method
CPRopertyPageImpL manages a PropsheetPage structure, which is public member M_PSP. CPRopertyPageImpL also overloads the PropsheetPage * operator, so you can pass CPropertyPageImp to methods that require the LPPropsheetPage or LPCPropsheetPage type, such as CPropertySheetImpl :: addpage ().
CPROPERTYPAGEIMPL constructor allows you to set the title of the page, the title is usually displayed on the Tab tab of the page:
CPropertyPageImpl (_U_Stringorid Title = (LPCTSTR) NULL)
If you don't want the property table to create a property page but want to create a page manually, you can call CREATE ():
Hpropsheetpage crete ()
CREATE () just calls CreatePropertySheetPage () with M_PSP to do parameters (). If you add an properties page to an already created property table or add an attribute page (for example, a property table that is not in the controlled property table), you only need to call the create () function.
The following three methods are used to set various titles text for the property page:
Void setTitle (_U_Stringorid Title) Void SetHeadert itle (LPCTSTR LPSTRHEADERTILE) VOID SetHeadersubtitle (LPCTSTSTHEADERSUBTILESNUBTILE)
The first method changes the text of the page label, and several other words used to set the top of the attribute page in the wizard97 style.
void enablehelp ()
Set the PSP_HASHELP flag in m_psp, which makes the help buttons for the property table when this page is activated.
Processing notification message
CPRopertyPageImpl has a message mapping process WM_Notify. If the notification code is the value of PSN_ *, OnNotify () will call the corresponding notification processing function. This uses the compilation phase virtual function mechanism, so that the derived class can be easily overloaded with these processing functions.
Due to the changes in WTL 3 and WTL 7, there are two different notification processing mechanisms. In WTL 3, the value returned to the value returned to the return value of the psn_ * message, for example, WTL 3 is processed this: Case PSN_WIZFINISH: LRESULT =! Pt-> onwizardFinish (); Break;
OnWizardFinish () expects to return to the True End Wizard, FALSE blocks the closing wizard. This method is very simple, but IE5's general control adds a new explanation for the return value of PSN_WizFinish processing, and he returns the handle of the window that needs to get the focus. The WTL 3 program will not use this feature because it dos the same processing on all non-0 return values. In WTL 7, OnNotify () does not change the return value of the PSN_ * message, the process function returns the legal value and the correct behavior specified in any document. Of course, in order to backward, WTL 3 still uses the current default mode of operation, to use the WTL 7 message processing method, you must add a line before INCLUDING ATLDLGS.H:
#define_wtl_new_page_notify_handlers
Writing a new code has no reason not to use the WTL 7 message processing function, so there is no way of message processing of WTL 3 here.
CPropertyPageImpl provides a default notification message processing function for all messages, and you can overreload the message processing function related to your program to complete a special action. The default message handler and the corresponding behavior are as follows:
Int OnsetACTIVE () - Allows the page to be activated
Bool Onkillactive () - Allows the page to become an inactive state
Int onapply () - Returns psnret_noError indicates that the application operation is successful
Void OnReset () - No corresponding action
Bool onQueryCancel () - Allowance to cancel operation
Int onwizardback () - Return to the previous page
Int OnwizardNext () - to the next page
INT_PTR OnWIZARDFINISH () - Allows the wizard to end
Void onhelp () - no corresponding action
BOOL ONGETOBJECT (LPNMOBJECTNOTIFY LPOBJECTNOTIFY) - No corresponding action
INT ONTRANSLATEACCELERATOR (LPMSG LPMSG) - Returns psnret_noError indicates that the message is not processed
HWnd ONQUERYITIIALFOCUS (HWND HWNDFOCUS) - Returns NULL indicates that the first control will be set to focus status in the Tab ORDER order.
Create a property sheet
All explanations of these classes are all finished, and now you need an example program to demonstrate how to use them. The example of this chapter is a simple SDI program that displays a picture in the client area and uses a total color fill background. The images and colors used can be set by a option dialog (a property sheet). There is also a wizard. (Later will be described later).
The simplest properties table
First create an SDI project with WTL's wizard and then add a property table for your dialog. First change the wizard to create a dialog style to make it look like a property page.
The first step is to remove the OK button because the property table does not want the property page to shut down. In Style Tab, change the dialog style to Child, Thin Border, select Title Bar, in More Styles Tab, select Disabled.
The second step (also the last step) is to create a property sheet in the onappaBout () handler, we use non-custom CPROPERTYET and CPROPERTYPAGE classes:
LResult CMAINFRAME :: onappabout (...)
{
CpropertySheet Sheet (_T ("About psheets");
CPROPERTYPAGE
Sheet.Addpage (pgabout);
Sheet.domodal ();
Return 0;
}
The result looks down to this: create a useful property page
Not every attribute page in each property table is like this simpler, most property page requires the derived class of CPROPERTYPAGEIMPL, so we will see a class now. We created a new property page to set the image displayed by the client area background, it looks like this:
The style of this dialog is the same as about the page. We need a new class to work with this property page, and we name it CollGroundoptspage. This class is derived from the CPRopertyPageImpl class, which has a CWindataExchange to support DDX.
Class CBackgroundOptsPage:
Public CPropertyPageImpl
Public CWindataExchange
{
PUBLIC:
Enum {IDD = IDD_BACKGROUND_OPTS};
// construction
Cupackgroundoptspage ();
~ Cbackgroundoptspage ();
// Maps
Begin_msg_map (CBackgroundOptsPage)
MSG_WM_INITDIALOG (OnInitDialog)
CHAIN_MSG_MAP (CPropertyPageImpl
END_MSG_MAP ()
Begin_DDX_MAP (CBackgroundOptspage)
DDX_RADIO (IDC_BLUE, M_NCOLOR)
DDX_RADIO (IDC_ALYSON, M_NPICTURE)
END_DDX_MAP ()
// Message Handlers
Bool OnInitdialog (HWND HWNDFOCUS, LPARAM LPARAM);
// Property Page Notification Handlers
Int onapply ();
// ddx variables
INT M_NCOLOR, M_NPICTURE;
}
Note about this class:
There is a public member named IDD to link the dialog box in the resource. Message mapping chains and CDIALOGIMPL are similar. The message mapping chain links the message into CPROPERTYPAGEIMPL, allowing us to handle notification messages related to the property table. There is an onapply () handler to save the user's selection when clicking the OK button in the property table.
ONApply () is very simple, it calls dodataExchange () updates the DDX variable, and then returns a code identification whether you can close this property sheet:
Int CBackgroundOptspage :: onapply ()
{
RETURN DODATAEXCHANGE (TRUE)? psnret_noerror: psnret_invalid;
}
We also have to add a Tools | Options menu to the main window to open the property sheet, the process of handling the function creates a property sheet, but adds a new hometribution page CBackgroundOptSpage.
Void CMAINFRAME :: ONOPTIONS (uint ucode, int nid, hwnd hwndctrl)
{
CpropertySheet Sheet (_T ("Psheets Options"), 0);
Cupackgroundoptspage pgBackground;
CPROPERTYPAGE
PGBACKGROUND.M_NCOLOR = m_view.m_ncolor;
PGBACKGROUND.M_NPICTURE = m_view.m_npicture; sheet.m_psh.dwflags | = psh_noApplynow;
Sheet.Addpage (pgBackground);
Sheet.Addpage (pgabout);
IF (idok == sheet.domodal ())
M_View.setBackgroundOptions (pgBackground.m_ncolor,
pgBackground.m_npicture);
}
The second parameter of the constructor of the attribute table is 0, indicating that the page of the index is 0 is initially visible, you can set it to 1, so that the attribute table displays about the page when the attribute table is displayed. Since it is a demo code, I steal a lazy, use a public variable to establish the Radio button of the CBACKGROPTSPAGE property page, directly assigning the initial value in the main window, read it when the user clicks the OK button of the property table come out.
If the user clicks on the OK button, Domodal () uses the IDOK, we notify the view window to use the new pictures and background colors. Below is a few screenshots showing a few different styles:
Create a better attribute form class
Creating attributes in onOptions () is a good idea, but use a lot of initialization code here is very bad, this is not CMAINFRAME to do things. A better way is to derive a new class from CPropertySheetImpl and complete these tasks in this class.
#include "backgroundoptspage.h"
Class CapppropertySheet: Public CPropertySheetImpl
{
PUBLIC:
// construction
CAPPPROPERTYSHEET (_U_Stringorid Title = (LPCTSTR) NULL,
Uint uStartPage = 0, hwnd hwndparent = null;
// Maps
Begin_msg_map (capppropertySheet)
Chain_msg_map (CPropertySheetiMPL
END_MSG_MAP ()
// Property Pages
CBackgroundOptsPage M_PGBackGround;
CPROPERTYPAGE
}
We use the details of each attribute page in this class package attribute table, move the initialization code to the inside of the property table, the constructor completes the addition of the page, and set other required signs:
CapppropertySheet :: CapppropertySheet (_U_Stringorid Title, uint ustartpage,
HWND HWNDPARENT:
CPropertySheetImpl
{
m_psh.dwflags | = psh_noApplynow;
AddPage (m_pgbackground);
Addpage (M_PGABOUT);
}
In this way, the onOptions () handling the function has become simpler:
Void CMAINFRAME :: ONOPTIONS (uint ucode, int nid, hwnd hwndctrl)
{
CapppropertySheet Sheet (_T ("psheets options"), 0); Sheet.m_pgBackground.m_ncolor = m_view.m_ncolor;
Sheet.m_pgbackground.m_npicture = m_view.m_npicture;
IF (idok == sheet.domodal ())
m_view.setBackgroundOptions (Sheet.m_pgBackground.m_ncolor,
Sheet.m_pgbackground.m_npicture);
}
Create a wizard properties of a wizard
Creating a wizard and creating a property table is very similar, this is not surprising, just try to add "Previous" and "Next" button. Like MFC, you need to overload the OnsetActive () function and call SetWizardButtons () to make the corresponding button. Let's start with a simple introduction page, its ID is IDD_WIZARD_INTRO:
Note that this page does not have a title bar text because all pages in the wizard usually have the same title, I am more willing to set these texts in the constructor of the CPROPERTYSHEETIMPL, then use this string resources for each page. That's why I only need to change a string to change all the page title text.
About this page implements code in the CWizInTropage class:
Class CwizInTropage: Public CPropertyPageImpl
{
PUBLIC:
ENUM {IDD = IDD_WIZARD_INTRO};
// construction
CWizInTropage ();
// Maps
Begin_MSG_MAP (COPTIONSWIZARD)
CHAIN_MSG_MAP (CPropertyPageImpl
END_MSG_MAP ()
// Notification Handlers
Int OnsetActive ();
}
Constructor uses (reference) a string resource ID to set the text of the page:
CWIZINTROPAGE :: CWizInTropage ():
CPROPERTYPAGEIMPL
{
}
When this page is activated, the string IDs_wizard_title ("psheets options wizard") will appear in the headline bar of the wizard. OnsetACTIVE () only allows the "Next" button available:
int CWizInTropage :: OnsetActive ()
{
SetWizardButtons (pswizb_next);
Return 0;
}
In order to implement a wizard, we need to create a class COPTIONSWIZARD and add menu Tools | Wizard in the main window. The constructor of the COPTIONSWIZARD class is the same as the constructor of the CAPPPRopertySheet class, just set the necessary style flags and add a page.
Class Coptionswizard: Public CPropertySheetImpl
{
PUBLIC:
// construction
COPTIONSWIZARD (HWND HWNDPARENT = NULL);
// Maps
Begin_MSG_MAP (COPTIONSWIZARD)
CHAIN_MSG_MAP (CPropertySheetImpl
END_MSG_MAP ()
// Property PageScWizInTropage M_PginTro;
}
COPTIONSWIZARD :: COPTIONSWIZARD (HWND HWNDPARENT):
CpropertySheetImpl
{
Setwizardmode ();
AddPage (m_pgintro);
}
The Tools | Wizard menu processing function of the CMAINFRAME class is this:
Void CMAINFRAME :: OnOptionswizard (uint ucode, int nid, hwnd hwndctrl)
{
COPTIONSWIZARD WIZARD;
WiZARD.domodal ();
}
This is the effect of the wizard:
Add more properties pages, use DDV
In order to make this wizard can be a bit used, we have to add a set view background color page for it. This page will also have a checkbox demonstrate how to process DDV validation failure and block the wizard to proceed to the next page. Below is the new page, ID is IDD_WIZARD_BKCOLOR:
This class's implementation code is in the CWIZBKCOLORPAGE class, below is related part of the code
Class CWIZBKCOLORPAGE:
Public CpropertyPageImpl
Public CWindataExchange
{
PUBLIC:
// Some Stuff Removed for BREVITY ...
Begin_DDX_MAP (CWIZBKCOLORPAGE)
DDX_RADIO (IDC_BLUE, M_NCOLOR)
DDX_CHECK (IDC_FAIL_DDV, M_BFAildDv)
END_DDX_MAP ()
// Notification Handlers
Int OnsetActive ();
Bool onkillactive ();
// DDX VARS
INT M_NCOLOR;
protected:
INT m_bfailddv;
}
OnsetActive () works the same as the previous introduction page, which makes "Previous" and "Next" buttons available. ONKILLACTIVE () is a new handset that triggers DDV, then checks the value of m_bfailddv, if you indicate that checkbox is selected, and OnkillaActive () will prevent the wizard to the next page.
int CWIZBKCOLORPAGE :: OnsetActive ()
{
SetWizardButtons (pswizb_back | pswizb_next);
Return 0;
}
INT CWIZBKCOLORPAGE :: ONKILLACTIVE ()
{
IF (! DODATAEXCHANGE (TRUE))
Return true; // prevent deactivation
IF (m_bfailddv)
{
Messagebox
_T ("Error Box Checked, Wizard Will Stay On this page."),
_T ("psheets"), MB_ICONERROR);
Return true; // prevent deactivation
}
Return False; // Allow deActiVation
}
It is important to note that things do in ONKILLACTIVE () can also be done in OnWizardNext () because both process functions can be maintained at the current page. The difference is that onkillactive () is called when the user clicks "Previous" and "Next" button, and onwizardNext () is only called when the user clicks the "Next" button. OnWizardNext () is also used to complete other purposes, for example, it can direct the wizard to the specified page instead of the next page in order. The wizards of the examples have two other pages, CWIZBKPicturePage and CWIZFINISHPAGE, because they are similar to the two pages, I will no longer introduce them in detail, I want to know their details to view the source code.
Other interface considerations
Set a property sheet
The default location of the attribute page and the wizard is to appear on the top left of the parent window:
This looks a bit uncomfortable, but it is good to remedy. The first method is to overrunate the CPROPERTYSHEETIMPL :: PropsheetCallback () function, in this function, in this function. PropSheetCallback () is a callback function of PropSheetProc () introduced in MSDN. The operating system calls this function when it is created, and WTL is also using this time subclassic property table window. So our first attempt is:
Class CapppropertySheet: Public CPropertySheetImpl
{
// ...
Static Int Callback PropsheetCallback (HWND HWND, UINT UMSG, LPARAM LPARAM)
{
INT nret = CPropertySheetImpl
HWND, UMSG, LPARAM;
IF (PSCB_Initialized == UMSG)
{
// Center Sheet ... Somehow?
}
Return nret;
}
}
As you can see, we have encountered tricky problems. PropSheetCallback () is a static method that cannot use the THIS pointer access to the property meter window. Then copy these code from cpropertySheetImpl :: propsheetcallback (), then add our own way to do it? A method that just linked the code and a specific version of the WTL (this has been proven to be a good method), the code should be like this:
Class CapppropertySheet: Public CPropertySheetImpl
{
// ...
Static int Callback PropsheetCallback (HWND HWND, UINT UMSG, LPARAM)
{
IF (umsg == pscb_initialize)
{
// code copied from wtl and Tweaked to use cappropertySheet
// instead of t:
Atlassert (hwnd! = Null);
CAPPPROPERTYSHEET * PT = (CAPPPROPERTYSHEET *)
_Module.extractcreateWnddata ();
// Subclass the Sheet Window
Pt-> SubclassWindow (HWND);
// Remove Page Handles Array
Pt -> _ cleanuppages ();
// Our Own Code Follows: Pt-> CenterWindow (Pt-> m_psh.hwndparent);
}
Return 0;
}
}
This is perfect in theory, but I tried, the location of the property sheet did not change. Obviously, the code of the general control has changed the location of the property table window after we call CenterWindow ().
This method of encapsulating the code into the properties form, although it is a good solution. I returned to the original solution, even if I use the Property Page window and the property table window to collaborate in the property table window. I added a user-defined message uWM_Center_Sheet:
#define uWM_center_sheet WM_APP
CapppropertySheet handles this message in its message map chain:
Class CapppropertySheet: Public CPropertySheetImpl
{
// ...
Begin_msg_map (capppropertySheet)
Message_handler_ex (UWM_Center_Sheet, ONPAGEINIT)
Chain_msg_map (CPropertySheetiMPL
END_MSG_MAP ()
// Message Handlers
LResult ONPAGEINIT (UINT, WPARAM, LPARAM);
protected:
Bool m_bcented; // set to false in the ctor
}
Lresult CapppropertySheet :: OnPageInit (Uint, WPARAM, LPARAM)
{
IF (! m_bcented)
{
m_bcented = true;
CenterWindow (M_PSH.HWNDPARENT);
}
Return 0;
}
Then, the OnInitDialog () method for each property page sends this message to the property table window:
Bool CBACKGROUNDOPTSPAGE :: OnNitDialog (HWND HWNDFOCUS, LPARAM LPARAM)
{
GetPropertySheet (). SendMessage (UWM_Center_Sheet);
DODATAEXCHANGE (FALSE);
Return True;
}
Add a M_BCentered flag to ensure that the property table window only responds to the first UWM_Center_Sheet message received.
Add icons in the property page
If you want to use the property table and the property page that is not encapsulated by the member function, you need to directly access the relevant data structure: the PropSheetHeader type (Structure) member M_PSHPAGE IMPL class in the CPropertySheetImpl class M_PSPAGE type (Structure) member M_PSP.
For example: Add an icon to the Background page in the Option property table, you need to add a logo and set several members in the PropsheetPage structure of the properties:
CBACKGROUNDOPTSPAGE :: CBackgroundoptspage ()
{
m_psp.dwflags | = PSP_USEICONID;
m_psp.pszicon = makeintResource (IDi_tabicon);
m_psp.hinstance = _Module.getResourceInstance ();
}
Below is the effect of these codes:
carry on
I will introduce some of the tool classes of WTL in Chapter 9, as well as packaging classes for GDI objects and general dialogs.
Modify record