Welcome!

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

Related Topics: PowerBuilder

PowerBuilder: Article

Overriding the PowerBuilder MessageBox() Function

Using a single global function to hasten evolution

A typical PowerBuilder application has a few zillion MessageBox() calls. This article explains how you can roll your own MessageBox() function, replacing the native PowerBuilder MB() function with your code.

Why would you want to write your own MB() function? The general reason is to give you a "hook" where you can write code to modify the native Windows MB() behavior. Let's face it, the MB() has evolved about as rapidly as the shark, like, not at all since PB 1.0.

And the MB() needs to evolve. Among the native MB()'s deficits:

  • It provides no way to get the text: you can't copy the text on a MB() into the clipboard.
  • It handles long messages badly: the buttons are below the bottom of the screen; and
  • Most aggravating, the PB MB() doesn't display if either its title or text is NULL!
How many times have you been burned by that last "feature"? Doh!

For these and other reasons, I've been wanting to rewrite MB() for years, but I couldn't.

It's easy to replace native PB functions: you just create a global function with the same name and the same signature, i.e., your function has the same argument types in the same order as the PB function.

For example, the native PB trim() function removes leading and trailing spaces, but I wanted it to remove tabs too - so I created my own trim() global function which does just that. Because it has the same signature as the native PB function, my custom trim() replaces the PB trim().

Other native PB functions that are candidates for replacement include:

beep()
Use the sndPlaySoundA() API function to make a sound with more pizzazz than the native beep(). You have a hook! Make it user-selectable! The guitar riff from "Bad to the Bone" is attention getting.

ProfileString() and SetProfileString()
Redefine these to use a database table instead of INI files, so a user's preferences aren't tied to a particular PC, and preferences work with terminal services like Remote Desktop and Citrix, where INI files can get dicey.

now(), today(), time()
Base these on SYSDATE taken from the database, so users can't falsify date stamps by resetting their PC system clock.

But I couldn't replace the MB() because it has a bunch of different signatures. The MB() has this syntax: MessageBox( title, text {, icon {, button {, default } } } )

So there are four ways to call the MB(). Defining a global MessageBox() function would only replace one of those types. Sure, I could replace the most common variant, the one that takes a title and text. That would leave the other variations unaltered, though, raising the terrifying specter of inconsistency (cue the scary minor key organ music).

But then I learned that you can overload global functions.

Yes, Virginia, there is a way to use one global function to overload all the various MessageBox() signatures. A single global function can have several different signatures embedded in its source. The PB IDE only lets you see one of them when you "Edit" the function, but all is revealed when you "Edit Source" instead. And PB has no problem about applying the multiple signatures.

So it's possible to override all of the MB() variants with a single global function: create a global MessageBox() function and embed a MB() function to match every possible native MB() signature. That's what I've done, and that's what this article is about. You'll learn how to write a global function that's overloaded with multiple signatures. You'll also get the functionality you want in those zillions of MB() calls in your application.

Why Override the MessageBox()?
For motivation, let's revisit why you'd want to replace the native PB function. First, let's address those deficits listed above. My custom MB() function:

  • Always puts its title and text into the clipboard, appending "(It's in the Clipboard)" to the title so the user knows.
  • It handles long messages by redirecting them to a window that uses a scrolling MLE to display the text, so all the text can be read and the buttons are accessible.
  • Best of all, the custom MB() replaces a NULL title or text with the string "NULL" so the MB() always appears!
That's not all, though. Once I had my custom MB() I soon thought of new opportunities for changing MB() behavior. Some of these may appeal to you too.

Logging errors
The legacy application I support is over-casual about logging errors. The framework puts some SQL errors into a log, but most of the time, past developers just put up a message box and that was that. My MB() detects logable events by looking for "error," "problem," or "fail" in the MB title and text, and logs the entire message when those magic strings are detected.

Adding a support message
Users like to know who to call when there's a problem. I created a means for our customers to specify a standard support message, e.g., "Please call the help desk at XXX-XXXX." The support message is appended to every "error" MB(), if the customer prefers.

Logging SQL error information My application has hundreds of MB()s that signify a database error, but don't present the SQLCode or the SQLErrText. "Update failed" isn't much help to the poor guy trying to support the legacy application (i.e., me). So my MB() checks to see if SQLCA contains error information, and (if so) whether it's different from the previously-logged error. If it's there and it's different then it's logged. This is a life-saver; there was previously no way to capture the details of those database failures.

Suppressing annoying MB()s
My application's policy is to prompt the user every time a window is closed using Cancel: "Are you sure you want to close without saving?" Ayup, that's why I hit the Cancel button! It starts get annoying the 10,000th time. It's particularly irksome because the code isn't smart enough to note that you haven't changed anything; it prompts you to save even though all you've done is open the window.

Unless you turn off that darn prompt. Given my custom MB(), I could offer users another preference. If they're willing to risk occasionally losing data because they Cancel instead of Save then they can turn the prompt off. Then my custom MB() detects the "Are you sure..." text and simply returns without displaying it.

Redirecting certain MB()s to MicroHelp
My application has places where the user is presented with multiple consecutive "Column X is required" messages, e.g., a "First name is required" MB() is displayed when you try to save. Fix that and your next save attempt produces "Last name is required." Fix that and the next required column produces yet another MB()...and pretty soon you want to take an ice pick to the UI. It's a bother because you have to close the MB() each time, which most of us do by clicking with the mouse. It's a pain the nth time you get the "Column X is required" message.

My custom MB() intercepts the "is required" messages and redirects them to MicroHelp with a beep so the user knows there's a problem. The user can simply see what needs to be fixed next and tab there and enter the data. No mouse needed.

Suppressing all MB()s
Soon after crafting my custom MB() I was creating an interface that loaded information from another system into one of my application's standard data entry screens. The problem was the validation code in the ItemChanged event: I needed to execute that code to detect problems with the data, but the validation code put up a MessageBox() that halted my interface code. Fortunately, I had a hook: with my custom MB(), flipping a Boolean suppresses the MB()s. I can do the ItemChanged validation without any MB()s interrupting. Without my custom MB() I would have had to do a lot of hand-coding in the ItemChanged to avoid those MB()s.

Halt the application
Have you ever gotten into one of those loops where the MB() appears over and over and over? Didn't you want to simply kill the application and go fix the bug? Rather than kill PB and have to restart it, use the magic key in your MB(), e.g., if F12 is down when the MB() closes then halt the application. This could be a development time-only feature:

// Halt close if F12 is pressed as the MB() closes (in the PB IDE only)
if handle( GetApplication() ) = 0 and keyDown( keyF12! ) then halt close


More Stories By Hoyt Nelson

Hoyt Nelson is an independent contractor, a 10-year PowerBuilder veteran, and father to the amazing Nelson, Kent, Emma, and Molly (Hi, kids!).

Comments (5) View Comments

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.


Most Recent Comments
Hoyt Nelson 01/27/09 07:13:00 PM EST

I never knew this article was here! Here's the code.

If you can't get it off this site, please email me at [email protected].

- Hoyt

A typical PowerBuilder application has a few zillion MessageBox() calls. This article explains how you can roll your own MessageBox() function, replacing the native PowerBuilder MB() function with your code.

Why would you want to write your own MB() function? The general reason is to give you a "hook" where you can write code to modify the native Windows MB() behavior. Let's face it, the MB() has evolved about as rapidly as the shark, like, not at all since PB 1.0.

------- snip -------
$PBExportHeader$messagebox.srf
$PBExportComments$Overloaded global MessageBox function
global type messagebox from function_object
end type

forward prototypes
global function integer MessageBox( string the_title, string the_message )
global function integer MessageBox( string the_title, string the_message, icon the_icon )
global function integer MessageBox( string the_title, string the_message, icon the_icon, button the_button )
global function integer messagebox (string the_title, string the_message, icon the_icon, button the_button, integer the_default)
end prototypes

global function integer MessageBox( string the_title, string the_message )
// MessageBox( string the_title, string the_message )

// Put it into the clipboard (and log it)
f_msg_box_to_clipboard( the_title, the_message )

// Default to no icon, OK!, button 1
return MessageBox( the_title, the_message, None!, OK!, 1 )
end function

global function integer MessageBox( string the_title, string the_message, icon the_icon )
// MessageBox( string the_title, string the_message, icon the_icon )

// Put it into the clipboard (and log it)
f_msg_box_to_clipboard( the_title, the_message )

/* The possible values for the_icon:

Information!
StopSign!
Exclamation!
Question!
None!

*/

// The icon determines the_buttons
if the_icon = Question! then
// Default to YesNo!, button 1 -> Yes
return MessageBox( the_title, the_message, the_icon, YesNo!, 1 )
else
// Default to OK!, button 1
return MessageBox( the_title, the_message, the_icon, OK!, 1 )
end if
end function

global function integer MessageBox( string the_title, string the_message, icon the_icon, button the_button )
// MessageBox( string the_title, string the_message, icon the_icon, button the_button )

// Put it into the clipboard (and log it)
f_msg_box_to_clipboard( the_title, the_message )

/* The possible values for the_button:

OK! (Default) OK button
OKCancel! OK and Cancel buttons
YesNo! Yes and No buttons
YesNoCancel! Yes, No, and Cancel buttons
RetryCancel! Retry and Cancel buttons
AbortRetryIgnore! Abort, Retry, and Ignore buttons

*/

// The_button determines the default
if the_button = OKCancel! or the_button = RetryCancel! then
// Default to 2 -> Cancel
return MessageBox( the_title, the_message, the_icon, the_button, 2 )
elseif the_button = YesNoCancel! then
// Default to 3 -> Cancel
return MessageBox( the_title, the_message, the_icon, the_button, 3 )
elseif the_button = OK! or the_button = YesNo! then
// Default to 1 -> OK and Yes
return MessageBox( the_title, the_message, the_icon, the_button, 1 )
elseif the_button = AbortRetryIgnore! then
// Default to 2 -> Retry
return MessageBox( the_title, the_message, the_icon, the_button, 2 )
else
g.bug( "Unexpected button in MessageBox( 4 arguments )" )
return -1 // Have to return something!
end if
end function

global function integer messagebox (string the_title, string the_message, icon the_icon, button the_button, integer the_default);/** integer messagebox( string the_title, string the_message )

This object contains four different variants on MessageBox():

global function integer MessageBox( string the_title, string the_message )
global function integer MessageBox( string the_title, string the_message, icon the_icon )
global function integer MessageBox( string the_title, string the_message, icon the_icon, button the_button )
global function integer MessageBox( string the_title, string the_message, icon the_icon, button the_button, integer the_default )

The first three correspond to "real" built-in PowerBuilder MessageBox() functions.
The last, which is this function you're looking at, is "unreal" in the sense that
PB includes NO MessageBox() that takes a string as the_default argument. This variant
exists as a ready replacement for MessageBox()s that take five arguments. This version
and the three "real" variants all call the "real" MessageBox() that has an integer as
the 5th argument (the_default.

You must edit this object by:

1) Exporting it to a directory, from the Library painter
2) Open that export, modify it, and save it
3) Import the object into its PBL in the Library Painter

If you open this object in the IDE, then you will ONLY see this version of the
MessageBox() function; you cannot see or modify the others. They are not lost,
however, if you modify this version and save it.

Experiment shows that the LAST function in the physical file is the one
opened in the global function editor of the IDE. So if you need to work
on one variant of the overloaded functions extensively, and you want to
use the IDE to do so -- move it to the last position in the file. The
order of the forward declarations at the top does NOT matter.

Experiment also shows that you can add instance variables to this object,
using the syntax applied by PB in windows and UserObjects. The object
will compile cleanly. However, THE INSTANCE REFERENCE IS INVALID. You'll
get a run-time system error. In practice, YOU CANNOT USE INSTANCE
VARIABLES. This is a global function, not an object, I guess.

Also, you can't have MessageBox( the_message ) -- just one argument. It
compiles but gives a run-time error.

Parameters: These are all the usual MessageBox() arguments, except the last

string the_title
string the_message
icon the_icon
button the_button
integer the_default

The_default is a value "1", "2" or "3", which will be passed as an integer to the "real" MessageBox()

Returns: integer, just like the "real" MessageBox()

Usage:

This example is the test script:

MessageBox( "Testing", "Two arguments" )
MessageBox( "Testing", "Three arguments", Information! )
MessageBox( "Testing", "Four arguments", Question!, YesNo! )
MessageBox( "Testing", "Five arguments", Question!, YesNo!, "1" )

Keywords: Overloading, global functions

See also: string(), another function that replaces the "real" PB function

Programmer: Hoyt Nelson, [email protected]

Revision History:

Hoyt 12/02/2005 Created this function

**/

// Put it into the clipboard (and log it)
f_msg_box_to_clipboard( the_title, the_message )

// Convert the_default to an integer
return MessageBox( the_title, the_message, the_icon, the_button, integer( the_default ) )
end function

Stephen Pareles 07/14/08 08:53:51 AM EDT

Nice article. Source code was unavailable. Any advice on getting it?
Steve

Kimmo Heimo 06/26/07 02:44:42 PM EDT

"As a result, you can import the MessageBox.srf from PBDJ and everything will be in place."

How can this be done? Where is the source?
I cannot find any download source feature!