| By Hoyt Nelson | Article Rating: |
|
| January 11, 2007 07:45 PM EST | Reads: |
12,692 |
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!
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!
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
Published January 11, 2007 Reads 12,692
Copyright © 2007 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
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!).
![]() |
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 hoyt.nelson@gmail.com. - 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 ------- forward prototypes global function integer MessageBox( string the_title, string the_message ) // Put it into the clipboard (and log it) // Default to no icon, OK!, button 1 global function integer MessageBox( string the_title, string the_message, icon the_icon ) // Put it into the clipboard (and log it) /* The possible values for the_icon: Information! */ // The icon determines the_buttons global function integer MessageBox( string the_title, string the_message, icon the_icon, button the_button ) // Put it into the clipboard (and log it) /* The possible values for the_button: OK! (Default) OK button */ // The_button determines the default 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 ) The first three correspond to "real" built-in PowerBuilder MessageBox() functions. You must edit this object by: 1) Exporting it to a directory, from the Library painter If you open this object in the IDE, then you will ONLY see this version of the Experiment shows that the LAST function in the physical file is the one Experiment also shows that you can add instance variables to this object, Also, you can't have MessageBox( the_message ) -- just one argument. It Parameters: These are all the usual MessageBox() arguments, except the last string the_title 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" ) Keywords: Overloading, global functions See also: string(), another function that replaces the "real" PB function Programmer: Hoyt Nelson, hoyt.nelson@gmail.com Revision History: Hoyt 12/02/2005 Created this function **/ // Put it into the clipboard (and log it) // Convert the_default to an integer |
||||
![]() |
Stephen Pareles 07/14/08 08:53:51 AM EDT | |||
Nice article. Source code was unavailable. Any advice on getting it? |
||||
![]() |
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? |
||||
- Why SOA Needs Cloud Computing - Part 1
- Cloud Expo and The End of Tech Recession
- The Transition to Cloud Computing: What Does It Mean For You?
- A Rules Engine Built in PowerBuilder
- Sybase Named “Silver Sponsor” of iPhone Developer Summit
- How PowerBuilder Got Its Groove Back
- The Cloud Has Cross-Border Ambitions
- Ulitzer Names The World's 30 Most Influential Virtualization Bloggers
- Ulitzer Named "New Media" Partner of Greatly Anticipated iStrategy Event in Berlin
- Risks and Enterprise Mobility?
- Steps for Success in Enterprise Mobility?
- Are Mobile Luddites Resisting Mobility?
- The Difference Between Web Hosting and Cloud Computing
- Sybase CTO to Speak at 4th International Cloud Computing Expo
- Why SOA Needs Cloud Computing - Part 1
- Cloud Expo and The End of Tech Recession
- The Transition to Cloud Computing: What Does It Mean For You?
- Five Reasons to Choose a Private Cloud
- Seeding The Cloud: The Future of Data Management
- The Threat Behind the Firewall
- Economy Drives Adoption of Virtual Lab Technology
- Tips for Efficient PaaS Application Design
- A Rules Engine Built in PowerBuilder
- Sybase Named “Silver Sponsor” of iPhone Developer Summit
- Where Are RIA Technologies Headed in 2008?
- PowerBuilder History - How Did It Evolve?
- The Top 250 Players in the Cloud Computing Ecosystem
- Custom Common Dialogs Using SetWindowsHookEx
- DDDW Tips and Tricks
- OLE - Extending the Capabilities of PowerBuilder
- DataWindow.NET How To: Data Entry Form
- Book Excerpt: Sybase Adaptive Server Anywhere
- Sybase ASE 12.5 Performance and Tuning
- Working with SOA & Web Services in PowerBuilder
- Office 2003 Toolbar: A New Look For Your Old PowerBuilder App
- Dynamically Creating DataWindow Objects

































