PBDJ Feature — Working with Windows Messages in PowerBuilder

PowerBuilder provides the send() function to send messages to the Windows objects. The information about its usage is fragmentary and widely dispersed throughout the help and manuals. Since searching for this information was quite tedious, I decided to write down what information I could gather.

I hesitated to start this article with the statement that PowerBuilder is a Windows application; it seemed ridiculous. However, that's the basis for this article. Windows has an event-driven user interface and we can use the events for our own purpose.

In addition to using the Windows functions to call the windows API, we can also send messages to Windows controls. Sometimes it's helpful to send messages to the controls since they're triggered by the operating system, for example, an emulated mouse click, a keystroke, or any other event.

In Windows, messages are sent with the Windows function:

SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );

And PowerBuilder script provides the same functionality in the send function. The syntax is:

Send ( handle, message#, lowword, long )

With:

Note: In Windows, not only Windows are "windows" but also almost every other visual control such as Buttons, Scrollbars, Editfields, etc., that you can get a handle to.

Here are same examples from the PowerBuilder helpfile:

This statement scrolls the window w_emp up one page:

Send(Handle(w_emp), 277, 2, 0)

The following statements click the CommandButton cb_OK:

Send(Handle(Parent), 273, 0, Handle(cb_OK))

We see that it's quite easy to send Windows messages if you know the message numbers and parameters, but how do you know them? This seemed (to me) to be the most difficult part. I've looked through all the help files but couldn't find a place that contains all the information about the numbers and parameters. So I started searching.

What I found: it's not too difficult to find the information you need about messages when you know where to search. You need access to the Windows API help, which can be found at http://msdn.microsoft.com/library/. Here you have a description of all the messages you can send.

You'll also need the winuser.h file. On most installations it can be found at: C:\Program Files\Sybase\Shared\PowerBuilder\cgen\h\nt\winuser.h. (A more complete version is the winuser.h that comes with MS Visual Studio.) While the API help uses constants like WM_KEYDOWN in its description, there are numerical equivalents in the winuser.h.

I could go on by just placing a table with the event IDs and parameters for you. However, as the saying goes: Give a man a fish, and you have fed him for today. Teach a man to fish, and he will sit in a boat and drink beer all day (or teach a man to fish, and you have fed him for a lifetime.)

So get your fishing rod (the "fish" will be at the end of the article ;-)) and go for the beer (or let us solve some practical problems).

How to Do It
The first task will be to tab out a field programmatically, which triggers the modified event of that field in some applications. This is easily done by emulating the message of a pressed Tab-Key from the keyboard, i.e., we have to send the message to the window that is the same as the one sent by the OS when we press Tab-Key. But what message is sent by the keyboard?

We have to look in the Windows API help. This is a challenge and you need luck as well as a keen understanding of the Windows API help. I've found that it's not that well structured and intuitive. However, if you know that we are looking for a Notification (see Figure 1), searching with "Key pressed notification" brings us near enough to find "WM_KEYDOWN" at:

Win32 and COM Development > User Interface > Windows User Interface > User Input > Keyboard Input > Notification

This is the adequate system message, which is described as follows:

The next step is to find the number of this system event. This is where winuser.h steps in. Here we search for WM_KEYDOWN and we find:

#define WM_KEYDOWN         0x0100

This is the definition of the Windows constant WM_KEYDOWN as hex 0x0100, which is dec 256. (I use the windows Calculator in the scientific view for conversion.)

wParam has to be the ASCII-Code for the Tab-Key which is 9. lParam is set to Zero.

Our call is: send(Handle(this), 256,9, long(0,0))

Scroll a Window
If you want to emulate a scroll-down event for a window object, you must find the event that is sent to the window when the scroll bar is moved. You can find it at:

Win32 and COM Development > User Interface > Windows Shell > Windows Controls > Individual Control Information > Scroll Bars >


WM_VSCROLL Notification
The WM_VSCROLL message is sent to a window when a scroll event occurs in the window's standard vertical scroll bar. This message is also sent to the owner of a vertical scroll bar control when a scroll event occurs in the control.

SYNTAX

WM_VSCROLL
    WPARAM wParam
    LPARAM lParam;

PARAMETERS
wParam
The high-order word specifies the current position of the scroll box if the low-order word is SB_THUMBPOSITION or SB_THUMBTRACK; otherwise, this word is not used.

The low-order word specifies a scroll bar value that indicates the user's scrolling request. This parameter can be one of the following values:

lParam
If the message is sent by a scroll bar, this parameter is the handle to the scroll bar control. If the message is not sent by a scroll bar, this parameter is NULL.

In winuser.h, we find that WM_VSCROLL is 0x0115 which is dec 277. SB_PAGEDOWN is 3.

    wParam is 3 for SB_PAGEDOWN.
    lParam is set to Zero.
    So send(Handle(this), 277,3, long(0,0)) scrolls one page down.
    Table 2 provides a list of some common events.

STRATEGIES FOR THE HELP
If you want to learn more about Window Controls or to look for something special, go to the MSDN at: MSDN Home > MSDN Library > Win32 and COM Development > User Interface > Windows Shell > Windows Controls and look for "Messages" and "Notifications" in the subtopics "General Control Information" and "Individual Control Information." Another approach is to look in the winusers.h for something that seems to suit your needs.

Send Messages to PowerBuilder Custom Events
You can also send messages to PowerBuilder custom events. You may need that to communicate between two PowerBuilder applications.

The pbm_custom01 event ID maps to wm_user+0, pbm_custom02 maps to wm_user+1, and so on, through pbm_custom75, which maps to wm_user+74. WM_USER is defined in winusers.h:

#define WM_USER    0x0400    = 124

Application Ping-Pong
Figure 2 and Figure 3 provides an example of a ping - pong between several instances of an application.

In the open event I searched for instances that have already started; this is shown in a listbox. In addition in an instance variable il_number I store the instance that I am. (die wievielte?)

For the interapplication communication, I have two events (ue_ping and ue_pong) that map to custom controls. Ue_ping receives a message from another instance where the sender's window handle and its Instance-Number are transmitted in the wparam and lparam. The event displays a messagebox and answers with a pong.

event ue_ping pbm_custom01
Messagebox("Here is window: "+string(handle(this)),
" Got a Ping from: " + string (wparam) + " with Instance No. "+string(lparam) )

// Pong to sender of the Ping (Event Id:1025)
send (wparam, 1025, handle(this), il_number ) ;
end event

ue_pong receives the message send from ue_ping where the sender's window handle and its Instance-Number are transmitted in the wparam and lparam displays a messagebox.

event ue_pong pbm_custom02;
Messagebox("Here is window: "+string(handle(this)),
" Got the Pong from: " + string (wparam) + " with Instance No. "+string(lparam) )
end event

To send a ping I type one of the window handles of a running instance in an edit-field (em_win_handle) and press a button where the script for the clicked event sends a "ping" to that window:

In the button Control clicked event
unsignedlong      hndl_me,&
      hndl_you

hndl_me = handle(parent)
hndl_you = long(em_win_handle.text)

IF hndl_you > 0 THEN
// Send a ping to pbm_custom01 and transmit my handle and instance number in the params
send (hndl_du, 1024, hndl_ich, il_nummer ) ;
ELSE
MessageBox("Oups", em_window.text + " is not running!")
END IF

More Examples
STARTING SYSCOMMANDS WITH SEND()
Another unusual use of the send() function is to start System Commands. If you search for SYSCOMMAND Notification, you'll find several parameters that trigger various actions, one of which is SC_SCREENSAVE to start the screensaver.

START SCREENSAVER
To start the screensaver, write:

/* ** WM_SYSCOMMAND 0x0112    274
** SC_SCREENSAVE 0xF140    61760
*/
send(handle(This),274,61760,0)

MAXIMIZE A FRAME
To maximize a window when it opens put the following code into the Open event:

CONSTANT Integer WM_SYSCOMMAND = 274
CONSTANT UInt SC_MAXIMIZE = 61488
//
Send(Handle(This), WM_SYSCOMMAND, SC_MAXIMIZE, 0)

MOVE A WINDOW WITHOUT A TITLEBAR
To move a window without a titlebar, place the following code in the mousedown event:

CONSTANT uint WM_NCLBUTTONDOWN = 161
CONSTANT uint HTCAPTION = 2

Post( Handle( this ), WM_NCLBUTTONDOWN, HTCAPTION, Long( xpos, ypos ) )

MAKE THE ENTER KEY ACT AS A TAB KEY
First, define a user event to correspond with the pbm_dwnprocessenter event on a DataWindow. In that event, code:

Send(Handle(this),256,9,Long(0,0))

OPEN A DDDW VIA POWERSCRIPT

[external function declaration]
SUBROUTINE keybd_event( int bVk, int bScan, int dwFlags, int dwExtraInfo) &
LIBRARY "user32.dll"

[powerscript]
constant integer VK_F4 = 115

dw_1.SetFocus()
dw_1.SetColumn( "dept_head_id" ) // the DDDW
keybd_event( VK_F4,0,0,0 ) // F4 key down
keybd_event( VK_F4,0,2,0 ) // F4 key up

© 2008 SYS-CON Media