6.3 Creating a Property Sheet Handler

only for RuBoard - do not distribute or recompile

6.3 Creating a Property Sheet Handler

In this chapter, we will create a property sheet extension that will allow us to modify every aspect of a .rad file: animal type, gender, color , age, weight, and noise. The property sheet we will create is shown in Figure 6.3.

Figure 6.3. RAD property sheet
figs/vshl.0603.gif

6.3.1 Implementing IShellExtInit

We will continue to use the RadEx project. But the first thing we need to do is to add the clsDropFiles class to the project (See Example 6.1). This class will handle the implementation of IShellExtInit . If you look at the class listing, you will see that it contains the code that was used previously in our implementation of IShellExtInit (see Chapter 4). The code is very generic. In fact, we will reuse the clsDropFiles class when we discuss drop handlers in Chapter 8.

After clsDropFiles has been included in the project, we need to add another class to the project called clsPropSheet. This class will contain everything necessary to implement a property sheet handler. Once this has been done, we can implement IShellExtInit in the clsPropSheet class module as follows :

 'clsPropSheet Option Explicit Implements IShellExtInit Implements IShellPropSheetExt Private m_clsDropFiles As clsDropFiles Private Sub IShellExtInit_Initialize( _          ByVal pidlFolder As VBShellLib.LPCITEMIDLIST, _          ByVal pDataObj As VBShellLib.IDataObject, _          ByVal hKeyProgID As VBShellLib.HKEY)     Set m_clsDropFiles = New clsDropFiles     m_clsDropFiles.GetDropFiles pDataObj, ".rad"      End Sub 

6.3.2 Creating a Dialog Resource

Before we actually implement IShellPropSheetExt , we need to add a resource file to the project that contains the dialog that will be our property sheet. People who program Windows in C/C++ do this every day. To a VB programmer, this might be a new experience.

The property sheet dialog will be defined as a resource that will be stored in our COM server along with the icons that were used for the icon handler. The resource file created for this book was done with the editor in Visual C++ shown in Figure 6.4, but feel free to use any tool you wish if you plan to create your own resource file. Be warned , coding dialog resources by hand is tedious , because you have to enter in the position of all the dialog items yourself, which is a guessing game at best. If you don't have Visual C++, find another resource editor. They are out there.

Figure 6.4. Creating a dialog template with Visual C++ resource editor
figs/vshl.0604.gif

For the masochists out there, Example 6.4 is the complete listing for handler.rc , the resource file used for this chapter. It can be compiled into an .res file by using a program called RC.EXE . This program ships with Visual Basic. You can compile from the command line (provided RC.EXE is in your path ) as follows:

 C:\>  rc handler.rc  

handler.rc makes reference to the icons used for our icon hander (see Chapter 5). These files need to be in the directory where the resource file is being compiled.

Example 6.4. Handler.rc Listing
 //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL)  defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ARMADILLO           ICON    DISCARDABLE     "armadill.ico" IDI_CAT                 ICON    DISCARDABLE     "cat.ico" IDI_COW                 ICON    DISCARDABLE     "cow.ico" IDI_DOG                 ICON    DISCARDABLE     "dog.ico" IDI_FISH                ICON    DISCARDABLE     "fish.ico" IDI_UNKNOWN             ICON    DISCARDABLE     "unknown.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE  BEGIN     "resource.h 
  //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL)  defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ARMADILLO ICON DISCARDABLE "armadill.ico" IDI_CAT ICON DISCARDABLE "cat.ico" IDI_COW ICON DISCARDABLE "cow.ico" IDI_DOG ICON DISCARDABLE "dog.ico" IDI_FISH ICON DISCARDABLE "fish.ico" IDI_UNKNOWN ICON DISCARDABLE "unknown.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_RADPROPDLG DIALOG DISCARDABLE 0, 0, 210, 154 STYLE WS_CHILD  WS_VISIBLE  WS_BORDER FONT 8, "MS Sans Serif" BEGIN LTEXT "Type",IDC_STATIC,21,18,20,10 LTEXT "Color",IDC_STATIC,21,76,22,8 LTEXT "Age",IDC_STATIC,21,95,18,9 LTEXT "Weight",IDC_STATIC,21,114,25,8 LTEXT "Noise",IDC_STATIC,21,131,32,8 COMBOBOX IDC_TYPE,60,17,73,69,CBS_DROPDOWN  CBS_HASSTRINGS  WS_TABSTOP GROUPBOX "Gender",IDC_STATIC,21,37,112,32,WS_GROUP CONTROL "Male",IDC_MALE,"Button",BS_AUTORADIOBUTTON,31,50,32,9 CONTROL "Female",IDC_FEMALE,"Button",BS_AUTORADIOBUTTON,79,50,40, 9 EDITTEXT IDC_COLOR,53,74,80,13,ES_AUTOHSCROLL COMBOBOX IDC_AGE,53,92,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP COMBOBOX IDC_WEIGHT,53,111,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP EDITTEXT IDC_NOISE,53,129,80,13,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_RADPROPDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 203 TOPMARGIN, 7 BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog Info // IDD_RADPROPDLG DLGINIT BEGIN IDC_TYPE, 0x403, 10, 0 0x7241, 0x616d, 0x6964, 0x6c6c, 0x006f, IDC_TYPE, 0x403, 4, 0 0x6143, 0x0074, IDC_TYPE, 0x403, 4, 0 0x6f43, 0x0077, IDC_TYPE, 0x403, 4, 0 0x6f44, 0x0067, IDC_TYPE, 0x403, 5, 0 0x6946, 0x6873, "\000" 0 END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// # ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED  
" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "
  //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL)  defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ARMADILLO ICON DISCARDABLE "armadill.ico" IDI_CAT ICON DISCARDABLE "cat.ico" IDI_COW ICON DISCARDABLE "cow.ico" IDI_DOG ICON DISCARDABLE "dog.ico" IDI_FISH ICON DISCARDABLE "fish.ico" IDI_UNKNOWN ICON DISCARDABLE "unknown.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_RADPROPDLG DIALOG DISCARDABLE 0, 0, 210, 154 STYLE WS_CHILD  WS_VISIBLE  WS_BORDER FONT 8, "MS Sans Serif" BEGIN LTEXT "Type",IDC_STATIC,21,18,20,10 LTEXT "Color",IDC_STATIC,21,76,22,8 LTEXT "Age",IDC_STATIC,21,95,18,9 LTEXT "Weight",IDC_STATIC,21,114,25,8 LTEXT "Noise",IDC_STATIC,21,131,32,8 COMBOBOX IDC_TYPE,60,17,73,69,CBS_DROPDOWN  CBS_HASSTRINGS  WS_TABSTOP GROUPBOX "Gender",IDC_STATIC,21,37,112,32,WS_GROUP CONTROL "Male",IDC_MALE,"Button",BS_AUTORADIOBUTTON,31,50,32,9 CONTROL "Female",IDC_FEMALE,"Button",BS_AUTORADIOBUTTON,79,50,40, 9 EDITTEXT IDC_COLOR,53,74,80,13,ES_AUTOHSCROLL COMBOBOX IDC_AGE,53,92,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP COMBOBOX IDC_WEIGHT,53,111,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP EDITTEXT IDC_NOISE,53,129,80,13,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_RADPROPDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 203 TOPMARGIN, 7 BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog Info // IDD_RADPROPDLG DLGINIT BEGIN IDC_TYPE, 0x403, 10, 0 0x7241, 0x616d, 0x6964, 0x6c6c, 0x006f, IDC_TYPE, 0x403, 4, 0 0x6143, 0x0074, IDC_TYPE, 0x403, 4, 0 0x6f43, 0x0077, IDC_TYPE, 0x403, 4, 0 0x6f44, 0x0067, IDC_TYPE, 0x403, 5, 0 0x6946, 0x6873, "\000" 0 END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// # ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED  
" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "
  //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL)  defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ARMADILLO ICON DISCARDABLE "armadill.ico" IDI_CAT ICON DISCARDABLE "cat.ico" IDI_COW ICON DISCARDABLE "cow.ico" IDI_DOG ICON DISCARDABLE "dog.ico" IDI_FISH ICON DISCARDABLE "fish.ico" IDI_UNKNOWN ICON DISCARDABLE "unknown.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_RADPROPDLG DIALOG DISCARDABLE 0, 0, 210, 154 STYLE WS_CHILD  WS_VISIBLE  WS_BORDER FONT 8, "MS Sans Serif" BEGIN LTEXT "Type",IDC_STATIC,21,18,20,10 LTEXT "Color",IDC_STATIC,21,76,22,8 LTEXT "Age",IDC_STATIC,21,95,18,9 LTEXT "Weight",IDC_STATIC,21,114,25,8 LTEXT "Noise",IDC_STATIC,21,131,32,8 COMBOBOX IDC_TYPE,60,17,73,69,CBS_DROPDOWN  CBS_HASSTRINGS  WS_TABSTOP GROUPBOX "Gender",IDC_STATIC,21,37,112,32,WS_GROUP CONTROL "Male",IDC_MALE,"Button",BS_AUTORADIOBUTTON,31,50,32,9 CONTROL "Female",IDC_FEMALE,"Button",BS_AUTORADIOBUTTON,79,50,40, 9 EDITTEXT IDC_COLOR,53,74,80,13,ES_AUTOHSCROLL COMBOBOX IDC_AGE,53,92,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP COMBOBOX IDC_WEIGHT,53,111,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP EDITTEXT IDC_NOISE,53,129,80,13,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_RADPROPDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 203 TOPMARGIN, 7 BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog Info // IDD_RADPROPDLG DLGINIT BEGIN IDC_TYPE, 0x403, 10, 0 0x7241, 0x616d, 0x6964, 0x6c6c, 0x006f, IDC_TYPE, 0x403, 4, 0 0x6143, 0x0074, IDC_TYPE, 0x403, 4, 0 0x6f43, 0x0077, IDC_TYPE, 0x403, 4, 0 0x6f44, 0x0067, IDC_TYPE, 0x403, 5, 0 0x6946, 0x6873, "\000" 0 END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// # ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED  
" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_RADPROPDLG DIALOG DISCARDABLE 0, 0, 210, 154 STYLE WS_CHILD WS_VISIBLE WS_BORDER FONT 8, "MS Sans Serif" BEGIN LTEXT "Type",IDC_STATIC,21,18,20,10 LTEXT "Color",IDC_STATIC,21,76,22,8 LTEXT "Age",IDC_STATIC,21,95,18,9 LTEXT "Weight",IDC_STATIC,21,114,25,8 LTEXT "Noise",IDC_STATIC,21,131,32,8 COMBOBOX IDC_TYPE,60,17,73,69,CBS_DROPDOWN CBS_HASSTRINGS WS_TABSTOP GROUPBOX "Gender",IDC_STATIC,21,37,112,32,WS_GROUP CONTROL "Male",IDC_MALE,"Button",BS_AUTORADIOBUTTON,31,50,32,9 CONTROL "Female",IDC_FEMALE,"Button",BS_AUTORADIOBUTTON,79,50,40, 9 EDITTEXT IDC_COLOR,53,74,80,13,ES_AUTOHSCROLL COMBOBOX IDC_AGE,53,92,32,55,CBS_DROPDOWN WS_VSCROLL WS_TABSTOP COMBOBOX IDC_WEIGHT,53,111,32,55,CBS_DROPDOWN WS_VSCROLL WS_TABSTOP EDITTEXT IDC_NOISE,53,129,80,13,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_RADPROPDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 203 TOPMARGIN, 7 BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog Info // IDD_RADPROPDLG DLGINIT BEGIN IDC_TYPE, 0x403, 10, 0 0x7241, 0x616d, 0x6964, 0x6c6c, 0x006f, IDC_TYPE, 0x403, 4, 0 0x6143, 0x0074, IDC_TYPE, 0x403, 4, 0 0x6f43, 0x0077, IDC_TYPE, 0x403, 4, 0 0x6f44, 0x0067, IDC_TYPE, 0x403, 5, 0 0x6946, 0x6873, "
  //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL)  defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ///////////////////////////////////////////////////////////////////////////// // // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_ARMADILLO ICON DISCARDABLE "armadill.ico" IDI_CAT ICON DISCARDABLE "cat.ico" IDI_COW ICON DISCARDABLE "cow.ico" IDI_DOG ICON DISCARDABLE "dog.ico" IDI_FISH ICON DISCARDABLE "fish.ico" IDI_UNKNOWN ICON DISCARDABLE "unknown.ico" #ifdef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog // IDD_RADPROPDLG DIALOG DISCARDABLE 0, 0, 210, 154 STYLE WS_CHILD  WS_VISIBLE  WS_BORDER FONT 8, "MS Sans Serif" BEGIN LTEXT "Type",IDC_STATIC,21,18,20,10 LTEXT "Color",IDC_STATIC,21,76,22,8 LTEXT "Age",IDC_STATIC,21,95,18,9 LTEXT "Weight",IDC_STATIC,21,114,25,8 LTEXT "Noise",IDC_STATIC,21,131,32,8 COMBOBOX IDC_TYPE,60,17,73,69,CBS_DROPDOWN  CBS_HASSTRINGS  WS_TABSTOP GROUPBOX "Gender",IDC_STATIC,21,37,112,32,WS_GROUP CONTROL "Male",IDC_MALE,"Button",BS_AUTORADIOBUTTON,31,50,32,9 CONTROL "Female",IDC_FEMALE,"Button",BS_AUTORADIOBUTTON,79,50,40, 9 EDITTEXT IDC_COLOR,53,74,80,13,ES_AUTOHSCROLL COMBOBOX IDC_AGE,53,92,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP COMBOBOX IDC_WEIGHT,53,111,32,55,CBS_DROPDOWN  WS_VSCROLL  WS_TABSTOP EDITTEXT IDC_NOISE,53,129,80,13,ES_AUTOHSCROLL END ///////////////////////////////////////////////////////////////////////////// // // DESIGNINFO // #ifdef APSTUDIO_INVOKED GUIDELINES DESIGNINFO DISCARDABLE BEGIN IDD_RADPROPDLG, DIALOG BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 203 TOPMARGIN, 7 BOTTOMMARGIN, 147 END END #endif // APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Dialog Info // IDD_RADPROPDLG DLGINIT BEGIN IDC_TYPE, 0x403, 10, 0 0x7241, 0x616d, 0x6964, 0x6c6c, 0x006f, IDC_TYPE, 0x403, 4, 0 0x6143, 0x0074, IDC_TYPE, 0x403, 4, 0 0x6f43, 0x0077, IDC_TYPE, 0x403, 4, 0 0x6f44, 0x0067, IDC_TYPE, 0x403, 5, 0 0x6946, 0x6873, "\000" 0 END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// # ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED  
0" 0 END #endif // English (U.S.) resources ///////////////////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ///////////////////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED

After you have handler.res in your possession (hopefully you just used the copy provided), include it in the project.

6.3.3 Implementing IShellPropSheetExt

IShellPropSheetExt contains two methods named AddPages and ReplacePage , as Table 6.1 shows. ReplacePage functions similarly to AddPages , but we will not write any code for this method; we'll just have the method return E_NOTIMPL . This is because ReplacePage is used to replace property sheet pages in Control Panel applications, not shell extensions. That leaves us with one method, and implementing it is no walk in the park, because we have to handle all the messages for the property sheet through a dialog procedure. This means that we have to deal with raw window messages. No events here. Tedious, to say the least. But we'll talk about this later.

6.3.3.1 AddPages

The implementation for this method is fairly involved, so let's slow way down. The first thing we need to do is populate a PROPSHEETPAGE structure that will describe our property sheet. Other than the specifics of this structure, AddPages is almost always implemented the same. This is handy to know should you want to come up with a more generic implementation later. The PROPSHEETPAGE is defined as follows in the Platform SDK:

 typedef struct _PROPSHEETPAGE {     DWORD dwSize;     DWORD dwFlags;     HINSTANCE hInstance;     union {         LPCSTR pszTemplate;         LPCDLGTEMPLATE pResource;         };     union {         HICON hIcon;         LPCSTR pszIcon;         };     LPCSTR pszTitle;     DLGPROC pfnDlgProc;     LPARAM lParam;     LPFNPSPCALLBACK pfnCallback;     UINT FAR * pcRefParent; #if (_WIN32_IE >= 0x0400)     LPCTSTR pszHeaderTitle;     LPCTSTR pszHeaderSubTitle; #endif } PROPSHEETPAGE, FAR *LPPROPSHEETPAGE; 

Notice the two union blocks in the structure. As VB programmers, many of you probably have never seen a union before. In C/C++, they are quite common. A union is simply two or more members of a structure that occupy the same address in memory. VB does not have the concept of a union, but there is a simple solution to this problem (in this case). We can just flatten out the structure. This means that we keep the member of the union that we need and toss out the rest. This is easy to do here; actually, it doesn't matter which member we keep, because both unions contain members that are of the same datatype (as far as we are concerned )pointers. The final IDL version of this structure (from Example 6.3) looks like this:

 typedef struct {     DWORD   dwSize   ;     DWORD   dwFlags   ;     HINSTANCE   hInstance   ;     LPCSTRVB pszTemplate;    // from union     HICON hIcon;             // from union         LPCSTRVB pszTitle;     DLGPROC pfnDlgProc;     LPARAM lParam;     LPFNPSPCALLBACK pfnCallback;     long pcRefParent;     LPCTSTRVB pszHeaderTitle;     LPCTSTRVB pszHeaderSubTitle; } PROPSHEETPAGE; 

Table 6.2 provides information on the members of this structure. Before we continue, familiarize yourself with the structure (see also Table 6.3). If you are also a C/C++ programmer, you can probably tell what most of the parameters are just by looking at them. If not, don't worry. We'll discuss each member in detail during the implementation of AddPages .

Table6.2. PROPSHEETPAGE Structure

Member

Description

dwSize

Size of this structure.

dwFlags

Option flags used when creating a property sheet page. (See Table 6.3.) These values may be Or ed together.

hInstance

Instance handle from which to load the icon or title string resource.

pszTemplate

Dialog box template used to create the property sheet. This can be a resource ID or the address of a string that points to the name of the template.

hIcon

If the PSP_USEHICON flag is specified, this should contain the handle of an icon to use in the property sheet. If PSP_USEICONID is specified, then this should be the resource ID of an icon to use in the property sheet.

pszTitle

Title of the property sheet.

pfnDlgProc

Address of the dialog procedure for the property sheet.

lParam

Application-defined data that will be passed to the dialog procedure.

pfnCallback

Address of an application-defined callback function that is called when the page is created and when it is about to be destroyed .

pcRefParent

Address of the reference count value. This value is ignored if the PSP_USEPARENT flag is not specified.

pszHeaderTitle

Title of the header area.

pszHeaderSubTitle

Subtitle of the header area.

Table6.3. dwFlags for PROPSHEETPAGE

Flag

Description

PSP_DEFAULT

The default meaning is used for all structure members.

PSP_DLGINDIRECT

Creates the page from the dialog box template in memory pointed to by pResource . pResource is actually pszTemplate in the VB version of PROPSHEETPAGE .

PSP_HASHELP

Enables the property sheet help button.

PSP_HIDEHEADER

Causes the header area to be hidden in a wizard property sheet.

PSP_PREMATURE

Causes the page to be created when the property sheet is created. Otherwise, the page will not be created until it is selected the first time.

PSP_RTLREADING

Causes the property sheet to display pszTitle using right-to-left reading order on Hebrew or Arabic systems.

PSP_USECALLBACK

Calls the function specified by pfnCallback when creating or destroying the property sheet page.

PSP_USEHEADERSUBTITLE

Displays the text in pszHeaderSubTitle as the subtitle of the header area.

PSP_USEHEADERTITLE

Displays the text in pszHeaderTitle as the title of the header area.

PSP_USEICONID

Uses pszIcon as the name of the icon resource to load and use as the small icon on the tab for the page.

PSP_USEREFPARENT

Maintains the reference count specified by pcRefParent for the lifetime of the property sheet page.

PSP_USETITLE

Uses pszTitle as the title of the property sheet dialog box instead of the title stored in the dialog box template.

Now that you are somewhat familiar with PROPSHEETPAGE , let's implement AddPages . The first thing we need to do is populate a PROPSHEETPAGE structure and define the behavior of our property sheet. Example 6.5 starts us off.

Example 6.5. AddPages
 Private Sub IShellPropSheetExt_AddPages( _              ByVal lpfnAddPage As VBShellLib.LPFNADDPROPSHEETPAGE, _              ByVal lParam As VBShellLib.lParam)     Dim psp As PROPSHEETPAGE     Dim sTitle As String     Dim lAddPage As Long  sTitle = StrConv("RAD Settings", vbFromUnicode)  With psp         .dwSize = Len(psp)         .dwFlags = PSP_USECALLBACK Or PSP_USETITLE         .hInstance = App.hInstance         .lParam = ObjPtr(Me)  .pfnDlgProc = GetAddress(AddressOf PropSheetDlgProc)         .pfnCallback = GetAddress(AddressOf PropSheetCallbackProc)  .pszTemplate = IDD_RADPROPDLG         .pszTitle = StrPtr(sTitle)     End With 

There is quite a bit more going on here than you might realize. Let's examine the pfnDlgProc and pfnCallback members of the PROPSHEETPAGE structure more closely. See the call to GetAddress ? This is a simple function that exists just to work around the fact that AddressOf cannot be used in an assignment. GetAddress is a simple hack and looks like this:

 'Called like this: 'lpAddr = GetAddress(AddressOf SomeFunc) Public Function GetAddress(ByVal lpfn As Long)     GetAddress = lpfn End Function 

In direct terms, GetAddress provides the address of a function for the right-hand side of an assignment. So, referring to Example 6.5, pfnDlgProc and pfnCallback are assigned the addresses of two functions: PropSheetDlgProc and PropSheetCallbackProc . These two functions are known as callback functions.

This is where things get tricky. In the world of VB, forms are created just by adding a form object to your project. Predefined events are provided for. Everything is nice and simple. All you need to do is add code for a specific event and presto , when you click on a command button your code is executed. If you have never programmed Windows in C, you probably don't realize how incredibly easy VB makes your life. You are about to get a glimpse of what it is like to program Windows in another language. We'll be using VB, of course, but the code will look more like something that is found in a C program. Why?

Remember, we have added a dialog template to the project that will be our property sheet. It would have been nice to use a VB form, but we just can't do that. We are restricted by the PROPSHEETPAGE structure, which requires us to provide a resource identifier to a dialog template. Because of this, we don't have access to the event procedures VB normally provides for us when we use forms. In order for this to work, we will have to handle event processing for the property sheet ourselves , without the benefit of VB events. This is where the two functions PropSheetDlgProc and PropSheetCallbackProc come in. (We'll discuss these two functions in detail in a moment.) All events for the dialog will be processed by these two functions. So, looking back at Example 6.5, the assignment to pfnDlgProc indicates that the function called PropSheetDlgProc will handle all the events for the dialog.

Let's get back to the PROPSHEETPAGE structure.

dwSize is set to the size of the structure itself. hInstance is set to the instance handle provided by the App object. dwFlags is set to PSP_USECALLBACK and PSP_USETITLE . This means the callback function specified by the pfnCallback member will be called twice, once when the page is created and once when it is destroyed. This also means that the string pointed to by the pszTitle member should be used as the title for the property sheet. Notice that the string is converted from Unicode to ANSI before the assignment.

lParam is an interesting member because it functions like the Tag property on many VB controls. It can hold any long value that we wish or none at all. We can do whatever we want with this parameter. In our case, it is assigned a pointer to our object. This is very fortunate for us, because once the property sheet is created, the events will be handled by our two callback procedures, PropSheetDlgProc and PropSheetCallbackProc . These two functions exist in a code module outside of our class. By assigning a pointer to our object to lParam , we can get a reference back to our object (should we actually need to) once the property sheet is up and running. This will become more evident when we actually code these two functions.

pszTemplate is assigned to IDD_RADPROPDLG , a constant that equals 106. Why 106? 106 just happens to be the resource identifier for the dialog template. If you were to open the copy of handler.rc provided with the book's downloadable source in the Visual C++ IDE, you would see that the value corresponds to the dialog. In any event, this value should represent the resource identifier of the dialog being used as the property sheet.

That's it for PROPSHEETPAGE . The next thing we need to do is to call AddRef on our property sheet handler. We do this to counteract a call to Release that the shell will call on our object. If we don't increase the reference count of our object, it will be destroyed before we ever even see the property sheet. Presumably, the shell does this so that the handler can be unloaded if the creation of the property sheet fails. Regardless, the burden is on us to keep our component alive .

Remember, though, that the methods of IUnknown are restricted. We can't simply call AddRef directly. One way around this problem is to declare a private member variable of type clsPropSheet. Then we could do something like this:

 Private m_refObject As clsPropSheet Private Sub IShellPropSheetExt_AddPages( _              ByVal lpfnAddPage As VBShellLib.LPFNADDPROPSHEETPAGE, _              ByVal lParam As VBShellLib.lParam) . . . .     Set m_refObject = Me 

This will add a reference to our object. But this is not too clear. We can do things the right way with a little workaround. What we need to do is to add our own version of IUnknown to the type library. This version of IUnknown , which we'll call IUnknownVB , will be used any time we need to call IUnknown methods on our objects.

This brings up an interesting point. One of the reasons we are using the older MKTYPLIB to compile our type library is that we can have duplicate entries like this in the type library. Since IUnknown is already defined in the type library with the inclusion of stdole2.tlb (see Appendix A ), MIDL will complain that the interface is already defined. MKTYPLIB will not. Thus, we use MKTYPLIB. The listing for IUnknownVB is shown in Example 6.6.

Example 6.6. IUnknownVB Interface
 [     uuid(00000000-0000-0000-C000-000000000046),     helpstring("IUnknownVB Interface"),     odl ] interface IUnknownVB {     HRESULT QueryInterface([in] REFIID priid,                             [in, out] VOID *ppvObject);                                 long AddRef(  );     long Release(  ); }; 

Now that we have an IUnknown that we can use, calling AddRef on our object is a trivial matter. It's also a little clearer than the first method. This is shown in Example 6.7, which is the continuation of our AddPages implementation.

Example 6.7. AddPages, Continued
 'AddRef Dim pUnk As IUnknownVB Set pUnk = Me pUnk.AddRef 

Okay, now we are ready to create the property sheet. We do this by calling the Win32 function CreatePropertySheetPage , which is found in comctl32.dll . The function is declared as follows:

 Public Declare Function CreatePropertySheetPage _      Lib "comctl32.dll" Alias "CreatePropertySheetPageA" _      (p As PROPSHEETPAGE) As Long 

Moving API Declarations to a Type Library

Although it is not done in this book, it is possible to declare API functions in a type library. Consider GetWindowText and SetWindowText . Both of these functions are defined in user32.dll . Their IDL definitions might look like this:

 [     dllname("user32.dll") ] module User32 {     [entry("GetWindowTextA")]     int _stdcall GetWindowText(         [in] long hwnd,         [in,out] LPSTR lpsz,         [in] int cbMax     );     [entry("SetWindowTextA")]          void _stdcall SetWindowText(         [in] long hwnd,         [in, out] LPSTR lpsz     ); } 

The listing is fairly self explanatory. The [dllname] attribute defines the location of the exported functions listed in the module block. This is nice, because you only have to define this location once, unlike using Declare statements in a module.

The [entry] attribute is analogous to the Alias keyword in Visual Basic. If you happen to have the ordinal of the exported function, you can use that in place of the string value.

The functions are defined exactly as they have been listed in the Platform SDK. Be careful if you are reverse-engineering definitions from your module files. Remember, an int here is 4 bytes, unlike VB, in which it is 2 bytes (for backward compatibility). Also, notice how Strings are declared as LPSTR . That's a pointer, hence the [out] attribute.

Using this paradigm, you could also define module blocks for the GDI and Kernel functions as well. It was not done in this book because we wanted to keep the type library as simple as possible and to leave the focus on the interfaces being defined.

The CreatePropertySheetPage function takes the PROPSHEETPAGE structure we have just finished populating as its only parameter. Upon successful completion of the call, we are returned a handle to the newly created property sheet, as Example 6.8 demonstrates .

We now have a handle to our newly created property sheet, if all has gone well.

Now things get strange . Look at the first parameter to AddPages , lpfnAddPage . This is a pointer to a function that we are supposed to call with hPage . There is no way to call a function through a pointer in VB. This function, which is pointed to by lpfnAddPage, actually displays the property sheet.

Although there is no way to call a function through a pointer in VB, it can be done in C++, and that is what we will do.

Example 6.8. AddPages, Continued
 Dim hPage As Long     hPage = CreatePropertySheetPage(psp)     If hPage Then         lAddPage = AddPropertyPage(lpfnAddPage, hPage, lParam)              If lAddPage = 0 Then             DestroyPropertySheetPage hPage         End If     End If End Sub 

There is a DLL included with the source in this chapter called propext.dll . You need to be move this to your system directory in order for the property sheet handler to function properly. This DLL contains one exported function, AddPropertyPage (shown inExample 6.9 ) , that will allow us to call the function given to us by the shell. The code for AddPropertyPage is shown in Example 6.6.

Example 6.9. AddPropertyPage (in C++)
 BOOL WINAPI AddPropertyPage(LPFNADDPROPSHEETPAGE  lpfnAddPage  ,                             HPROPSHEETPAGE hPage,                             LPARAM lParam) {     // In C++ you can call a function through     // an address. return(  lpfnAddPage  (hPage, lParam)); } 

If a nonzero result is returned, DestroyPropertySheetPage is called to free memory associated with the property sheet.

6.3.4 PropSheetCallbackProc

This function is called once when the property sheet is being created and once right before it is to be destroyed. It is not called by us, but by Explorer. It is defined as follows:

 UINT CALLBACK PropSheetPageProc(HWND   hwnd   ,                                  UINT   uMsg   ,                                  LPPROPSHEETPAGE   ppsp   ); 

with the following parameters:

hwnd

This parameter is reserved. It should be NULL .

uMsg

This is the action flag. It will be equal to one of two values: PSPCB_CREATE (2) upon creation of the property sheet or PSPCB_RELEASE (1) when the property sheet is being destroyed.

ppsp

This is a pointer to the PROPSHEETPAGE structure used to create the property sheet.

The implementation of this function is fairly simple. We do not need to process any messages in this function. It will be used only to get a reference to the property sheet object over to the module file. What does this mean exactly? Well, once the property sheet is up and running, we will not have access to the property sheet handler object anymore. This is because the two callback functions that handle all of the property sheet messages are located in a code module outside of the class.

Remember the lParam member of our PROPSHEETPAGE structure (see Example 6.5)? It's a pointer to our object. The ppsp parameter of PropSheetPageProc is a pointer to our original PROPSHEETPAGE structure. Therefore, we have access back to our object through this parameter. This is good because it keeps us from having to resort to a global variable. It also provides a good opportunity to show you how to copy an object without incrementing its reference count. These are good tricks to know if you find yourself doing any advanced COM programming in the future. Example 6.10 details PropSheetCallbackProc , which is located in a code module called PropSheet.bas . All of the items specific to our property sheet implementation will be located in this file.

Example 6.10. The PropSheetCallbackProc Function
 'Property Sheet object reference  Private m_pPropSheet As clsPropSheet  Public Function PropSheetCallbackProc(ByVal hwnd As hwnd, _                                        uMsg As MSG, _  ByVal ppsp As Long  ) As Long     Dim psp As PROPSHEETPAGE     CopyMemory psp, ByVal ppsp, Len(psp)  'Get reference to object. No AddRef!!!!!     CopyMemory m_pPropSheet, psp.lParam, 4  Select Case uMsg                  Case PSPCB_CREATE             'Return non-zero to create page. 0 prevents it.         Case PSPCB_RELEASE:             'Page is being destroyed. Return value is ignored.                  End Select               PropSheetCallbackProc = 1          End Function 

We are given a pointer to a PROPSHEETPAGE structure, ppsp , as a parameter to the callback. This pointer is copied to a local instance of PROPSHEETPAGE using CopyMemory . This technique should be very familiar to you now. Once we have a local copy of PROPSHEETPAGE , we can get access to the lParam member of the structure. The lParam member of ppsp contains a pointer to our property sheet extension. This pointer can be used to create a copy of our object. There will be no call to AddRef . We have a copy of the object, and its reference count is one. This is a very important distinction. Because we have a copy of the object itself, we cannot set the object equal to Nothing when we are done with it. I will talk about how to free this copy when I discuss PropSheetDlgProc , the second callback function, later in the chapter.

The rest of this function is very straightforward. There are only two messages this callback will receive: PSPCB_CREATE and PSPCB_RELEASE . This callback is called only twice, once for each message. These messages are not important to us, since VB provides us with Initialize and Terminate events to handle startup and shutdown operations. In addition to those two events, we also have access to the Window procedure for the dialog box itself, which means we can process WM_INITDIALOG and WM_DESTROY messages ourselves.

6.3.5 PropSheetDlgProc

This function is responsible for processing all of the events for the property sheet. It is known as a DLGPROC , and its syntax is as follows:

 BOOL CALLBACK DialogProc(HWND   hwndDlg   ,                           UINT   uMsg   ,                          WPARAM   wParam   ,                          LPARAM   lParam   ); 

DialogProc is just a generic name for a dialog procedure. When you create your own dialog procedures, you can name them whatever you want (i.e., PropSheetDlgProc ).

The parameters to the DialogProc function are:

hwndDlg

Handle of the property sheet.

uMsg

The message being received by the property sheet.

wParam

Additional message-specific information.

lParam

Additional message-specific information.

The real action for the property sheet extension happens in PropSheetDlgProc , which is the message handler for the entire dialog box. This function is more complex than PropSheetCallbackProc only in that we are dealing with a few more Windows messages. We are not dealing with events here, but rather with raw Windows messages. These messages are passed in to us and processed by a Select statement, as Example 6.11 illustrates. Windows programs written in C process messages in this exact way. They are notorious for having gigantic switch (the C version of Select Case ) statements sometimes hundreds of lines long. Fortunately for us, there are only three messages we are interested in: WM_INITDIALOG (sent when the property page is created), WM_NOTIFY (sent for all control events), and WM_DESTROY (sent when the property sheet is destroyed). This makes our life somewhat easier.

Example 6.11. Dialog Procedure for Property Sheet
 Public Function PropSheetDlgProc(ByVal hwndDlg As hwnd, _      ByVal uMsg As UINT, _      ByVal wParam As wParam, _      ByVal lParam As lParam) As BOOL     Select Case uMsg                  Case WM_INITDIALOG             InitDialog hwndDlg                      Case WM_NOTIFY             Notify hwndDlg, lParam                      Case WM_DESTROY             'DO NOT DO THIS: Set m_pPropSheet = Nothing.             CopyMemory m_pPropSheet, 0&, 4          End Select          PropSheetDlgProc = 0      End Function 

Let's discuss the messages we are processing. Notice how these messages correspond to VB events. This is no coincidence . Under the hood, VB traps these same messages and provides events for you. It probably works something like this (ignoring the fact that VB is not actually coded in VB):

 Select Case uMsg                  Case WM_INITDIALOG             Call Form_Load . . . 
6.3.5.1 WM_INITDIALOG

The first message we will process is WM_INITDIALOG , which is analogous to the Form_Load event. We use this opportunity to populate drop-down lists with their appropriate values, read Animal data, and configure the dialog so that it displays the information found in the currently selected .rad file. As you can see, the handler for the message has been farmed out to a helper function called InitDialog . By creating helper functions to handle the messages, the dialog procedure remains less cluttered and is easier to work with (should we want to handle additional messages).

Let's take a peek at InitDialog , which is shown in Example 6.12. It's pretty tedious and repetitive, but it should make you more than a little grateful that you don't have to code your dialogs like this every day (unlike you C/C++ programmers out there). Take note of the SendDlgItemMessage and SetWindowText calls. These two API functions are the workhorses of InitDialog .

SendDlgItemMessage is how we send messages to "windows" (as opposed to Windows sending messages to us). Functions like SendDlgItemMessage and SendMessage are how the various "properties" of controls and windows get set. Even though many of these controls are COM objects themselves , under the covers, the work gets done by a call to SendMessage somewhere. This function is responsible for things like adding items to list boxes or combo boxes, removing items from list boxes or combo boxes, and setting and clearing checkboxes and radio buttons . The list goes on and on. There are literally hundreds of messages that various controls can receive. It is definitely the Swiss Army knife of the API world. For our property sheet, SendDlgItemMessage will be used to populate the Animal, age, and weight drop- downs .

SetWindowText does just what it says. Whenever you set the caption of a form, a label, button, or text box, you can bet that a call to this function is being made somewhere. SetWindowText will be used to populate all of the textboxes on our property sheet.

Because the . rad file mimics the format of an .ini file, retrieving data from the file is as simple as using a call to GetPrivateProfileString .

Example 6.12. WM_INITDIALOG Handler
 Public Sub InitDialog(ByVal hwndDlg As hwnd)     Dim c As Byte     Dim sz As String     Dim hwnd As hwnd  'Populate Animal type dropdown  sz = StrConv("Armadillo", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)     sz = StrConv("Cat", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)     sz = StrConv("Cow", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)     sz = StrConv("Dog", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)     sz = StrConv("Fish", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)     sz = StrConv("Unknown", vbFromUnicode)     SendDlgItemMessage hwndDlg, IDC_TYPE, CB_ADDSTRING, 0, StrPtr(sz)  'Populate Age dropdown  For c = 1 To 20         sz = StrConv(str(c), vbFromUnicode)         SendDlgItemMessage hwndDlg, IDC_AGE, CB_ADDSTRING, 0, StrPtr(sz)     Next c  'Populate Weight dropdown  For c = 1 To 200         sz = StrConv(Str(c), vbFromUnicode)         SendDlgItemMessage hwndDlg, IDC_WEIGHT, CB_ADDSTRING, _              0, StrPtr(sz)     Next c  'Set Animal type  sz = Space(255)     GetPrivateProfileString "Animal", "Type", "Unknown", sz, _         Len(sz), m_pPropSheet.SelectedFile     sz = TrimNull(sz) 'chop off NULL and convert to ANSI          hwnd = GetDlgItem(hwndDlg, IDC_TYPE)     SetWindowText hwnd, sz  'Set Animal Age  sz = Space(255)     GetPrivateProfileString "Animal", "Age", "1", sz, _         Len(sz), m_pPropSheet.SelectedFile  'Set combo = animal age  SendDlgItemMessage hwndDlg, IDC_AGE, CB_SETCURSEL, _          Int(sz) - 1, 0  'Set Animal Weight  sz = Space(255)     GetPrivateProfileString "Animal", "Weight", "1", sz, _         Len(sz), m_pPropSheet.SelectedFile  'Set combo = animal weight  SendDlgItemMessage hwndDlg, IDC_WEIGHT, CB_SETCURSEL, _          Int(sz) - 1, 0  'Set Animal gender  sz = Space(255)     GetPrivateProfileString "Animal", "Gender", "M", sz, _         Len(sz), m_pPropSheet.SelectedFile     If UCase(Left(sz, 1)) = "M" Then         SendDlgItemMessage hwndDlg, IDC_MALE, BM_SETCHECK, 1, 0     Else         SendDlgItemMessage hwndDlg, IDC_FEMALE, BM_SETCHECK, 1, 0     End If  'Get Animal color  sz = Space(255)     GetPrivateProfileString "Animal", "color", "unknown", sz, _         Len(sz), m_pPropSheet.SelectedFile     sz = LCase(TrimNull(sz))              hwnd = GetDlgItem(hwndDlg, IDC_COLOR)     SetWindowText hwnd, sz  'Set Animal noise  sz = Space(255)     GetPrivateProfileString "Animal", "Noise", "unknown", sz, _         Len(sz), m_pPropSheet.SelectedFile     sz = LCase(TrimNull(sz))              hwnd = GetDlgItem(hwndDlg, IDC_NOISE)     SetWindowText hwnd, sz End Sub 
6.3.5.2 WM_NOTIFY

Notification messages are sent by controls to the parent window for a variety of reasons. When the property sheet is selected, a notification is sent. When it is deselected, a notification is sent. Notifications are also sent when OK, Cancel, and Apply are pressed. And finally, a notification is sent right before the property sheet is about to be destroyed. Basically, a WM_NOTIFY message is sent whenever the control needs to notify its parent (our property sheet) of an event.

When a WM_NOTIFY message is sent, lParam contains a pointer to an NMHDR structure. CopyMemory can then be used to copy this address into a local instance of NMHDR . The structure is defined as follows:

 typedef struct {      HWND hwndFrom;      UINT idFrom;      UINT code;  } NMHDR; 

with the following members:

hwndFrom

The Windows handle of the control sending the message.

idFrom

The ID of the control.

code

The message being sent.

We will ignore every member except for code in our WM_NOTIFY event handler, which is shown in Example 6.13.

Example 6.13. WM_NOTIFY Handler
 Public Sub Notify(ByVal hwndDlg As hwnd, ByVal lParam As lParam)     Dim nh As NMHDR          CopyMemory nh, ByVal lParam, Len(nh)          Select Case nh.code         Case PSN_APPLY             'OK and Apply             SaveProperties hwndDlg                      Case PSN_QUERYCANCEL             'Cancel has been clicked. Return 1 to prevent. 0 to allow.                      Case PSN_SETACTIVE             'sent when property tab is selected for first time                      Case PSN_KILLACTIVE             'sent when another property tab is selected                      Case PSN_RESET             'Cancel has been allowed. About to be destroyed              End Select              End Sub 

The various messages that will be processed are listed with comments about their meaning, but the only message we are interested in is PSN_APPLY . This message is sent when OK or Apply has been pressed. We are not implementing Apply, so this distinction is not important. When OK is pressed, SaveProperties is called, and all the information from the dialog is written back into the .rad file.

SaveProperties , which is shown in Example 6.14, is very similar to InitDialog . In fact, it is almost its inverse. Where we used SetWindowText before, now we use GetWindowText . WritePrivateProfileString (alias WPPString ) handles writing the information back to the .rad file.

Example 6.14. SaveProperties
 Public Sub SaveProperties(ByVal hwndDlg As hwnd)     Dim lret As Long     Dim sz As String     Dim hwnd As hwnd          'Save animal type     sz = Space(255)     hwnd = GetDlgItem(hwndDlg, IDC_TYPE)     GetWindowText hwnd, sz, Len(sz)     sz = Trim(sz)     'WritePrivateProfileString     WPPString "Animal", "Type", sz, m_pPropSheet.SelectedFile          'Save gender     lret = SendDlgItemMessage(hwndDlg, IDC_MALE, BM_GETCHECK, 0, 0)     If lret Then         WPPString "Animal", "Gender", "M", m_pPropSheet.SelectedFile     Else         WPPString "Animal", "Gender", "F", m_pPropSheet.SelectedFile     End If          'Save color     sz = Space(255)     hwnd = GetDlgItem(hwndDlg, IDC_COLOR)     GetWindowText hwnd, sz, Len(sz)     sz = Trim(sz)     WPPString "Animal", "Color", sz, m_pPropSheet.SelectedFile          'Save age     lret = SendDlgItemMessage(hwndDlg, IDC_AGE, CB_GETCURSEL, 0, 0)     sz = str(lret + 1)     WPPString "Animal", "Age", sz, m_pPropSheet.SelectedFile          'Save weight     lret = SendDlgItemMessage(hwndDlg, IDC_WEIGHT, CB_GETCURSEL, 0, 0)     sz = str(lret + 1)     WPPString "Animal", "Weight", sz, m_pPropSheet.SelectedFile          'Save noise     sz = Space(255)     hwnd = GetDlgItem(hwndDlg, IDC_NOISE)     GetWindowText hwnd, sz, Len(sz)     sz = Trim(sz)     WPPString "Animal", "Noise", sz, m_pPropSheet.SelectedFile      End Sub 
6.3.5.3 WM_DESTROY

The last message we are interested in is WM_DESTROY , which provides us with an opportunity to free the reference to our object. But remember, we are dealing with a copy of the object (see Example 6.7). It has a reference count of 1. Explorer has a valid reference to the property sheet extension. When Explorer is shut down, it will try to call IUnknown::Release to free the reference it is holding. If we set m_pPropSheet equal to Nothing , it will be released from memory, and the reference held by Explorer will no longer be valid. This translates into a crash the next time Explorer is closed. Instead of setting m_ pPropSheet equal to Nothing , will be copied to its address to mark the memory as free. This is demonstrated in Example 6.8.

only for RuBoard - do not distribute or recompile


Visual Basic Shell Programming
Visual Basic Shell Programming
ISBN: B00007FY99
EAN: N/A
Year: 2000
Pages: 128

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net