The Second Cut

The Explorer Hook Procedure

To repeat: This discussion is within the context of Borland C++ Builder, not a pure API program, Here is an example of an Explorer-Style Hook Procedure. It really doesn't do much, except to illustrate how to operate the controls added with a custom template, and how the dialog messages arrive at the Hook Procedure.

Although every window has a Window Procedure, Win32 Common Dialogs have a Dialog Procedure as well. Microsoft explains that the default dialog procedure for a Common Dialog operates the default controls. Messages for any added controls are sent to the Explorer Hook Procedure. But keep in mind that some of the messages to the Hook Procedure are about the routine operation of the dialog, and will have to be passed on to the VCL's Explorer Hook Procedure. In any case, the messages sent to the Explorer Hook are never sent to the WndProc for the dialog window.

This is actually the entire code for the derived component, but the largest function is the Explorer Hook Procedure.

In ObsOpDlg.h


#ifndef ObsOpDlgH
#define ObsOpDlgH
// Just for reference, BCB translation of VCL Pascal:
// bool(__stdcall TCommonDialog::DialogHook * DLGHK)(HWND hwnd, UINT msg,
//     WPARAM wParam, LPARAM);

// This typedef is used in one declaration in the .cpp
typedef UINT (APIENTRY *ExplorerHookProc)
       (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);


class TObsOpenDialog : public TOpenDialog
{
// typedef Dialogs::TOpenDialog inherited;

private:
// Function pointer for user's OnSelectionChange event
   Classes::TNotifyEvent FOnSelectionChange;

protected:
// Derived Dialog's code, followed by call to user's Event, if any
   DYNAMIC void __fastcall DoSelectionChange(void);

__published:
// Getter and Setter for the function pointer declared above
   __property Classes::TNotifyEvent OnSelectionChange = {read=FOnSelectionChange,
       write=FOnSelectionChange};

public:
   __fastcall TObsOpenDialog(TComponent * Owner);// Constructor

// Must override faulty code in run-time package for TaskModalDialog
   BOOL __fastcall TObsOpenDialog::TaskModalDialog(void * DialogFunc, void
       *DialogData);

};
// Note that this is a *static* function, not a real member function.
// It is like a Windows Callback, so it does not know which instance of
// TObsOpenDialog supplied the address
   UINT APIENTRY ObsOpenDialogHook(HWND hwnd, UINT msg,
       WPARAM wParam, LPARAM lParam);
#endif

In ObsOpDlg.cpp:


#include 
#pragma hdrstop
#include "dlgs.h"
#include "commctrl.h"
#include "ObsOpDlg.h"
#include "custids.h"

// Note that all of these will be the same for all TOpenDialogs!
ExplorerHookProc oldhookproc;

__fastcall TObsOpenDialog::TObsOpenDialog(TComponent * Owner) :
   TOpenDialog(Owner)
{
Title="A derived dialog";
// Template="MYOPNDLG";  Would go here if TaskModalDialog were correct!
}

// Illustration of overriding the default OnSelectionChange event for
// the derived Open Dialog class
void __fastcall TObsOpenDialog::DoSelectionChange(void)
{
char c[MAX_PATH+1];

strncpy(c,FileName.c_str(),MAX_PATH);

// All this does is copy the filename, with complete path information,
// into an a second, added edit control. I could just as well have chosen
// to display the file size, which does not normally appear in the dialog.
SetDlgItemText((static_cast<TOpenDialog*>(Handle)),
    edSHOWCMNT,( LPCTSTR)c);

// Duplicate Dialogs::TOpenDialog::DoSelectionChange function code
if (FOnSelectionChange)             // If user set Event function,
    FOnSelectionChange(this);       // ..execute user's function too
}
// The following VCL code would not be needed if the user is NOT using
// run-time packages, but in case she is not, this is the only way to make
// the Explorer-Style Custom Template take effect.  References (in Delphi):
// http://www.mers.com/MERLIST/BORLAND/PUBLIC/DELPHI/VCL/COMPONENTS/USING/71370.HTML
// http://www.mers.com/MERLIST/BORLAND/PUBLIC/DELPHI/VCL/COMPONENTS/WRITING/37683.HTML
// (repaste long links if word-wrapped)

BOOL __fastcall TObsOpenDialog::TaskModalDialog(void * DialogFunc, void
   *DialogData) // override Dialogs::TOpenDialog method
{
((LPOPENFILENAMEA)DialogData)->Flags =
   ((LPOPENFILENAMEA)DialogData)->Flags | OFN_ENABLETEMPLATE;
((LPOPENFILENAMEA)DialogData)->lpTemplateName = "MYOPNDLG";
((LPOPENFILENAMEA)DialogData)->hInstance =
   (void*)FindClassHInstance(this->ClassType());
oldhookproc = ((LPOPENFILENAMEA)DialogData)->lpfnHook;
((LPOPENFILENAMEA)DialogData)->lpfnHook =ObsOpenDialogHook;
return TCommonDialog::TaskModalDialog(DialogFunc, DialogData);
}

// The Hook Function itself
UINT APIENTRY ObsOpenDialogHook(
   HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static int onceonly=false; // Will fail in multiple instances!
static HWND hw;
static NMHDR * nmh;

if(!onceonly)
   {
   ::SendMessage(GetParent(static_cast<TOpenDialog*>(hwnd)),
       CDM_SETCONTROLTEXT, IDOK,(long)"&Hooked");
// Controls hidden to create even more space in the dialog.  The
// file-type filter is not needed for this application anyway.
   ::SendMessage(GetParent(static_cast<TOpenDialog*>(hwnd)),
       CDM_HIDECONTROL	, cmb1, 0); // Files of type
   ::SendMessage(GetParent(static_cast<TOpenDialog*>(hwnd)),
       CDM_HIDECONTROL	, stc2, 0); // Label for combo box above
   onceonly=true;
   }

switch (msg)
   {
// case BN_CLICKED: *never*

// This message occurs *before* the dialog is initialized;
// See note below on CDN_INITDONE message
   case WM_INITDIALOG:
       {
       ShowMessage("WM_INITDIALOG");
       break;
       }

   case WM_CTLCOLORDLG:
       {
//     The following message is not shown in a ShowMessage()
//     because it interferes with drawing the dialog!  When
//     I tried it, the ShowMessage was not even Modal.
       SetDlgItemText((static_cast<TOpenDialog*>(hwnd)),
           edSHOWNAME,"WM_CTLCOLORDLG");
       break;
       }

   case WM_NOTIFY:
       {
       nmh= (LPNMHDR) lParam;

//     As noted in the MS documentation, the controls have *not*
//     been positioned until this message is received.
       if(nmh->code== CDN_INITDONE)
           {
           ShowMessage("CDN_INITDONE ");
           break;
           }

//     This message produces the VCL Event OnSelectionChange
//     You'll find that this message is also sent when the dialog opens,
//     although no file name is selected at that time.
       if(nmh->code== CDN_SELCHANGE)
           {
           ShowMessage("CDN_SELCHANGE");
           break;
           }

//     This message does not occur unless there is something
//     in the filename edit box when the user presses "Open".
       if(nmh->code== CDN_FILEOK)
           {
           ShowMessage("CDN_FILEOK");
           break;
           }
       break;
       }

       case WM_COMMAND:
           {
           hw=(HWND)lParam;

//         Illustration of user's custom template button press
           if (LOWORD(wParam) == (pbPRNTFILE))
               {
               SetDlgItemText(GetParent(static_cast<TOpenDialog*>(hwnd)),
                   IDCANCEL,(LPCSTR)"&Clicked!"); // Default control
               SetDlgItemText((static_cast<TOpenDialog*>(hwnd)),
                   edSHOWNAME,(LPCTSTR)"Clicked!!"); // Added control...
               break;
               }
           break;
           }

       case WM_DESTROY:
           {
           ShowMessage("WM_DESTROY");
           break;
           }
   } // msg switch bracket

// Note that it is necessary to call the original user Hook procedure, because
// it was installed by the VCL, when the TOpenDialog was created.  Among other
// things, the VCL hook centers the dialog on the screen.  It also allows
// detection of the messages that cause the VCL Events for TOpenDialog

oldhookproc(hwnd,msg,wParam,lParam);

// Microsoft Q86720 says, "... the hook receives all messages addressed to
// the dialog box. With the exception of the WM_INITDIALOG message, the
// hook function receives messages before its associated common dialog box
// does. If the hook function processes a message completely, it returns
// TRUE. If the common dialog box must provide default processing for a
// message, the hook function returns FALSE." 

return false;		// But in fact call default proc
}

#pragma package(smart_init)

Note that there are two ways to put text into a dialog control: Messaging, and SetDlgItemText(), the equivalent macro. They are both used above. If your custom template adds an Edit control, you can fill it like this in BCB:


SetDlgItemText((static_cast<TOpenDialog*>(hwnd)),edSHOWCMNT,(LPCTSTR)"Clicked!!");

where edSHOWCMNT is a #define for your numeric control identifier from the custom template. Note that this is one of the few cases where you use a Dialog handle directly, not its' parent, in a Common Dialog task. That's because the window created by the system for a hooked dialog is the hwnd sent with Hook Procedure messages. That's how BCB ends up with the handle of a child window for a TOpenDialog; the dialog is always hooked by the VCL code, regardless of whether you provide a Hook Procedure.

On the other hand, if you want to fill in one of the default Open Dialog controls in your code, the call looks like this:


SetDlgItemText(GetParent(static_cast<TOpenDialog*>(hwnd)),IDCANCEL,(LPCSTR)"&Clicked!");

if coded inside the Hook Procdure. Note that HWND hwnd is how you refer to the dialog that has sent the message. Because the Hook Procedure is a non-member static function, this is the only way to differentiate between instances of the new class.

Back to BCB Examples



Copyright © 2001 Timothy H. Buchman
Trademark notices     Privacy statement
Back to Home
 
Published: October 14, 2001
Modified: December 23, 2001