This discussion originally appeared in a Borland newsgroup, as noted below. It was not moved to the archive at MERS, so I retrieved it from Google. I've edited the message and added more material. Some of the research was done by Jonathan Halterman, as well as the authors "linked to" in this article.
Although this discussion is based on the Microsoft Windows SDK note titled "Explorer-Style Hook Procedures", and adjacent documents, there is not enough documentation on using these techniques with the VCL wrappers for the Windows dialogs. In particular, all of the material has not, to my knowledge, been gathered together in one place before.
Whether you use pure API, or start with the VCL wrapper, TOpenDialog, customization involves appearance, functionality, or both. In the API approach, appearance is adjusted with an Explorer-Style Custom Template. Functionality, including processing control actions (both for controls added with a Custom Template, and for the default controls) is adjusted by providing an Explorer-Style Hook Procedure. In fact, every TOpenDialog is hooked by the VCL, providing the opportunity to center the dialog, and to provide the VCL Events for TOpenDialog.
This document is an introduction to modifying TOpenDialog that way. Specific details about the Hook Procedure and the Custom Template are in the linked documents. The principal material here is how to force the VCL to recognize your API customizations, despite some errors in the run-time VCL code.
The following needs might contribute to a decision to use this Microsoft-documented and prescribed means of customizing the Open File (and related) dialogs:
1)The desire to process the dialog messages that can only be received by an Explorer-Style Hook procedure. One example of this would be controlling the contents of the main listview window of the dialog, by accepting or discarding each arriving item for display in the listview. An particularly good example of this is at http://www.codeguru.com/dialog/QuickLaunch.shtml .
2)The determination that the WinAPI method may be more robust over future releases of Windows. I have no opinion to express on that idea.
3)The desire to use a Windows dialog resource as the source of the controls to be added to the Open Dialog. Note that this includes a decision to create space *above* or to the *left* of the basic controls (the "static rectangle") in the Open Dialog. There is no other way to put controls above or to the left.
Although it's beyond the scope of this discussion, I want to note that I have had trouble getting the (shrink-wrapped) TOpenDialog to implement every single option offered in the VCL property TOpenDialog::Options. I believe this is due to errors in the VCL code, and that overriding TaskModalDialog provides the place to "or" in the desired options to the API options member of the OPENFILENAME structure. (OPENFILENAMEA is the VCL typedef for that structure.) Don't overlook that the size and composition of that structure has changed in the last few versions of Windows.
The message can be found at:
http://groups.google.com/groups?hl=en&selm=3b334ba9%242_1%40dnews:
From: "Timothy H. Buchman"Mauricio D...Newsgroups: borland.public.cppbuilder.winapi References: <3b321a32_2@dnews> Subject: Re: Adding a Control to a Common Dialog Date: Fri, 22 Jun 2001 09:41:32 -0400
Start by reading these sites: http://thunder.prohosting.com/~cbdn/cd005.htm
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/commdlg_39gz.asp
In case this link has moved, as they often do at MSDN, you're looking for the documents titled "Explorer-Style Hook Procedures", "Open and Save As Dialog Box Customization", and "Explorer-Style Custom Templates".
Because there is a bug in the Borland run-time code, you also have to override a VCL routine like this. I derived from TOpenDialog, but the process is the same for any of the Windows Common Dialogs. Whether or not you "see" this error depends on whether you set your Project Options to produce a stand-alone .exe or not. Without the override below, unless you uncheck "Build with Run-Time Packages", you won't even be able to get the Open Dialog to see your Custom Template!
in header:
public: __fastcall TObsOpenDialog(TComponent * Owner);// Constructor BOOL __fastcall TObsOpenDialog::TaskModalDialog(void * DialogFunc, void *DialogData);in .cpp:
__fastcall TObsOpenDialog::TObsOpenDialog(TComponent * Owner) :
TOpenDialog(Owner)
{
}
BOOL __fastcall TObsOpenDialog::TaskModalDialog(void * DialogFunc,
void* DialogData) // override Dialogs::TOpenDialog method
{
// Next two statements can be omitted if you don't plan to use a
// template, but only the Explorer Hook.
((LPOPENFILENAMEA)DialogData)->Flags =
((LPOPENFILENAMEA)DialogData)->Flags | OFN_ENABLETEMPLATE;
((LPOPENFILENAMEA)DialogData)->lpTemplateName = "MYOPNDLG";
((LPOPENFILENAMEA)DialogData)->hInstance =
(void*)FindClassHInstance(this->ClassType());
return TCommonDialog::TaskModalDialog(DialogFunc, DialogData);
}
The example in the first link above is static text, so it's quite straightforward. I don't think it would be much harder to read the contents of an added Edit after you press the "Open" button. But if you want to receive button presses from buttons in your dialog template, or if you want to see each keystroke in an added Edit, you have to include an Explorer-Style Hook Procedure, which is well-documented on MSDN. I don't have BCB5, but the Help suggests that ExplorerHook() may be exposed for overriding, which it is not in BCB3. In BCB3, you'd set your own hook like this, with the code above, in TaskModalDialog. I've found that it is not necessary to restore the swapped pointer, because the OPENFILENAME structure is not used after the dialog is destroyed:
ExplorerHookProc oldhookproc; // Declaration oldhookproc = ((LPOPENFILENAMEA)DialogData)->lpfnHook; ((LPOPENFILENAMEA)DialogData)->lpfnHook=ObsOpenDialogHook;
Since every TOpenDialog is hooked by the VCL, you should end your hook procedure with
oldhookproc(hwnd,msg,wParam,lParam);so that the usual TCommonDialog VCL events will occur. Note that your hook procedure cannot be a member function, it's a static function like this:
UINT APIENTRY ObsOpenDialogHook(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
That means that it's essential to refer to the calling dialog by the hwnd argument. It's the only way to tell which instance of your Open Dialog class called the static function.
|