Welcome!

PowerBuilder Authors: Chris Pollach, Yeshim Deniz, Jayaram Krishnaswamy, Kevin Benedict, Avi Rosenthal

Related Topics: PowerBuilder

PowerBuilder: Article

Custom Common Dialogs Using SetWindowsHookEx

There's No End To The Customization You Can Do


CWPRETSTRUCT * cwpretStruct = (CWPRETSTRUCT*)(lParam);

if ( ( cwpretStruct->message == CB_ADDSTRING ) && ( m_filetypes ) )
{

If m_filetypes is populated, we loop through those file types and determine if the file type that was just added is one we want to filter:


pArrayItemCount = m_pSession->GetArrayLength(m_filetypes) ;
for( i=1; i <= pArrayItemCount; i++)
{
pbFileType = m_pSession->GetStringArrayItem ( m_filetypes, dim, bIsNull );
lpszFileType = (LPSTR)m_pSession->GetString(pbFileType) ;
if ( strcmp ( (LPCTSTR) cwpretStruct->lParam, lpszFileType ) == 0 )

If so, we get a handle to the newly added dropdown item by sending a CB_FINDSTRINGEXACT message to the dropdown list box:


lResult = SendMessage( (HWND)cwpretStruct->hwnd,
(UINT)CB_FINDSTRINGEXACT, (WPARAM)0, (LPARAM)lpszFileType );

Once we have that, we send a CB_DELETESTRING message to the dropdown list box to remove the item from the list:


lResult = SendMessage ( (HWND)cwpretStruct->hwnd,
(UINT)CB_DELETESTRING, (WPARAM)lResult, (LPARAM)0 ) ;

If the message wasn't the CB_ASDSTRING, we check to see if it was the CB_SETCURSEL (the message that is fired after all the items have been added and the dialog creator sets the default value). If it is that message and our m_default member has been populated (it may not have been), we use the CB_FINDSTRING message to find the position within the dropdown list of the file type we want to make the default:


if ( ( cwpretStruct->message == CB_SETCURSEL ) && ( m_default ) )
{
lResult = SendMessage( (HWND)cwpretStruct->hwnd,
(UINT)CB_FINDSTRING, (WPARAM)0, (LPARAM)m_default );

Once we have that, we send our own CB_SETCURSEL message to set the default to that index value.


lResult = SendMessage ( (HWND)cwpretStruct->hwnd,
(UINT)CB_SETCURSEL, (WPARAM)lResult, (LPARAM)0 ) ;

But wait! That's going to cause the callback to fire again and we'll get another CB_SETCURSEL message. We could end up with recursion, except that we're also testing the value of the index that's being passed in the original message before we fire our message. If the default value that's being set is the value we want, we don't send our message, so we don't get recursion.

Calling in from PowerBuilder
What does it look like when you need to use it? Fortunately, it's a lot simpler than it was to create it:


customsaveas lnv_saveas
string ls_filetypes[]

ls_filetypes[1] = 'XML'
ls_filetypes[2] = 'PDF'
ls_filetypes[3] = 'XSL-FO'

lnv_saveas = CREATE customsaveas
lnv_saveas.register ( Handle ( parent ) )
lnv_saveas.filterfiletypes (
ls_filetypes )
lnv_saveas.setdefaultfiletype ( 'Excel5 with headers' )

dw_1.SaveAs ( )

Destroy lnv_saveas

Here we've declared a local instance of the extension. We don't want to use an instance variable or anything with a larger scope, because we're relying on the destructor of the object to unregister the hook. Otherwise we would need to add an additional Unregister method.

We then create an instance of the extension, call the register method passing in the handle of the window that is about the make the call, and then call either the filterfiletypes method, the setdefaultfiletype method, or (as in this case) both methods. Once we've done that, we call the SaveAs method of the DataWindow control. Immediately after the SaveAs call, we destroy the local instance of the extension.

What happens at runtime is that while the dialog is being created, PowerBuilder sends CB_ADDSTRING messages to the dialog to add file types to the dropdown list box. As the dialog finishes with each of those messages, our callback method is called and - when necessary - the file type is deleted from the list. Similarly, after PowerBuilder has finished adding all of its file types, it sends the CB_SETCURSEL message to set the default file type to "Text with headers," at which point our callback method is called again and we set the default file type to "Excel 5 with headers" instead. The end result is that the DDLB in the SaveAs dialog no longer has those file types and that Excel5 with headers is the new default (see Figure 1).

Note: If you do use the FilterFileTypes method, you'll generally want to call the SetDefaultFileType method as well, even if you don't want to change the default that PowerBuilder initially sets. The issue is that PowerBuilder attempts to set the default file type by specifying the index position it thinks that file type has. Since we're going in behind it and deleting file types, the index value that it's sending is no longer the file type that is the intended default.

The Fly in the Ointment
Wouldn't it be easier to wait until the original dialog creator is done with the dialog and then make all the changes afterward? Yes, it sure would be. That's generally the first approach you want to take when customizing a dialog. Instead of sending WH_CALLWNDPROCRET to the SetWindowsHookEx function, you would use WH_CBT instead. Originally designed to support computer-based training applications, the hook is invoked (among other times) anytime a dialog has finished being created and is about to be activated.

In fact, if you use that hook instead and then invoke a MessageBox and attempt to manipulate the dialog, it works well. The problem with the SaveAs dialog is that it appears that PowerBuilder is attaching to that same hook to do its own customization of the dialog. When we add our hook, we're actually getting control of the dialog before PowerBuilder has had a chance to make its changes. That's why, at least for this sample, I used the WH_CALLWNDPROCRET hook instead.

That's Just the Beginning
Once you've got a hook to the dialog, there's really no end to the customization that you can do. Removing a few items from a dropdown list box and changing its default value is just the beginning. You can move controls around, rename text on them, or even add controls of your own.

More Stories By Bruce Armstrong

Bruce Armstrong is a development lead with Integrated Data Services (www.get-integrated.com). A charter member of TeamSybase, he has been using PowerBuilder since version 1.0.B. He was a contributing author to SYS-CON's PowerBuilder 4.0 Secrets of the Masters and the editor of SAMs' PowerBuilder 9: Advanced Client/Server Development.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.