Event processing in visual extensions

A visual extension can have a window procedure that can process any Windows message or user-defined message. The PBVM passes all such messages to the visual extension, which can intercept messages and either process or ignore them.

WindowProc is an application-defined callback function that processes messages sent to a window. In the example in this section, a WM_PAINT message is sent to the extension when an action in the PowerBuilder application causes the window to be redrawn. When the extension receives the message, it repaints an area in the window using the current values for text and color set by the user of the application.

The following example also captures mouse clicks and double clicks, and triggers the Onclick and Ondoubleclick event scripts in the PowerBuilder application. You can use two different syntaxes for describing events:

event returnType eventName(args_desc) newline
event eventName pbevent_token newline

Using an event name with return type and arguments

The following description uses the first syntax. The class has two events, onclick and ondoubleclick:

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription()
{
   static const TCHAR desc[] = {
      "class visualext from userobject\n"
      "event int onclick()\n"
      "event int ondoubleclick()\n"
      "subroutine setcolor(int r, int g, int b)\n"
      "subroutine settext(string txt)\n"
      "end class\n" 
   };
return desc;
}

Capturing messages and mouse clicks

The code in the extension captures the Windows messages that cause the window to be drawn, as well as mouse clicks and double clicks:

LRESULT CALLBACK CVisualExt::WindowProc(
                                   HWND hwnd,
                                   UINT uMsg,
                                   WPARAM wParam,
                                   LPARAM lParam
                                   )
{
   CVisualExt* ext = (CVisualExt*)::GetWindowLong(hwnd,
      GWL_USERDATA);
   switch(uMsg) {

   case WM_CREATE:
      return 0;

   case WM_SIZE:
      return 0;

   case WM_COMMAND:
      return 0;

   case WM_PAINT: {
      PAINTSTRUCT ps;
      HDC hdc = BeginPaint(hwnd, &ps);
      RECT rc;
      GetClientRect(hwnd, &rc);
      LOGBRUSH lb;
      lb.lbStyle = BS_SOLID;
 
// Get color using the visual class's GetColor method
      lb.lbColor = ext->GetColor();
      HBRUSH hbrush = CreateBrushIndirect(&lb);
      HBRUSH hbrOld = (HBRUSH)SelectObject(hdc,
         hbrush);
      Rectangle(hdc, rc.left, rc.top, rc.right-rc.left,
         rc.bottom-rc.top);
      SelectObject(hdc, hbrOld);
      DeleteObject(hbrush);


// Get text using the visual class's GetText method
      DrawText(hdc, ext->GetText(), 
         ext->GetTextLength(), &rc, 
      DT_CENTER|DT_VCENTER|DT_SINGLELINE);
      EndPaint(hwnd, &ps);
      }
      return 0;

// Trigger event scripts in the PowerBuilder application
   case WM_LBUTTONUP:
      ext->TriggerEvent("onclick");
      break;

   case WM_LBUTTONDBLCLK:
      ext->TriggerEvent("ondoubleclick");
      break;
   }
   return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Triggering click events

The following is the TriggerEvent method that triggers the Onclick and Ondoubleclick events:

void CVisualExt::TriggerEvent(LPCTSTR eventName)
{
   pbclass clz = d_session->GetClass(d_pbobj);
   pbmethodID mid = d_session->GetMethodID(clz,
      eventName, PBRT_EVENT, "I");

   PBCallInfo ci;
   d_session->InitCallInfo(clz, mid, &ci);
   d_session->TriggerEvent(d_pbobj, mid, &ci);
   d_session->FreeCallInfo(&ci);
}

Using an event name with a PowerBuilder event ID

A simpler way to trigger events in a visual extension uses direct mapping of Windows messages to PowerBuilder events. The following class description contains the same two events, but in this case they use the alternative syntax that maps the event name to a PowerBuilder token name:

PBXEXPORT LPCTSTR PBXCALL PBX_GetDescription()
{
   static const TCHAR desc[] = {
      "class visualext from userobject\n"
      "event onclick pbm_lbuttonup\n"
      "event ondoubleclick pbm_lbuttondblclk\n"
      "subroutine setcolor(int r, int g, int b)\n"
      "subroutine settext(string txt)\n"
      "end class\n" 
   };
return desc;
}

Generating event syntax automatically

Importing the extension generates the Onclick and Ondoubleclick events with the appropriate arguments automatically, and at runtime, the PBVM fires the events. You do not need to capture the Windows messages WM_LBUTTONUP and WM_LBUTTONDBLCLK in the extension.

In the following description, onclick is the event name and pbm_lbuttonup is the event token name. Notice that the event name is not followed by empty parentheses as it is when you use the return type and arguments technique for describing the event:

 "event onclick pbm_lbuttonup\n"

About the token name

The token name is a string that maps to an internal PowerBuilder event ID defined in the header file pbevtid.h. The first ID in this file is PB_NULL. For all other IDs in the file, there is a fixed relationship between the name that you use in the description and the event ID in pbevtid.h. The name is the same as the ID with the letter m appended to the pb prefix. You must use lowercase in the description.

For example, the event ID PB_ACTIVATE in pbevtid.h maps to the token name pbm_activate. In the description provided with PBX_GetDescription, you must use the name pbm_activate. If the event name you provide does not exist, importing the extension generates an error message. See the pbevtid.h file for a complete list of mapped IDs.

Processing events sent to the parent of the window

Some Windows messages, such as WM_COMMAND and WM_NOTIFY, are sent to the parent of an object and not to the object itself. Such messages cannot be caught in the visual extension's window procedure. The PBVM calls the GetEventID method to process these messages, as follows:

  • If the message is mapped to a PowerBuilder event, GetEventID returns the event's identifier, for example PB_BNCLICKED, and the event is fired automatically.

  • If the message is not mapped to an event, GetEventID returns the value PB_NULL and the message is discarded.

In the following example, the GetEventID function returns the identifier PB_BNCLICKED if a WM_COMMAND message with the notification code BN_CLICKED was sent. It returns the identifier PB_ENCHANGE if a WM_NOTIFY message was sent. Otherwise, it returns PB_NULL.

TCHAR CVisualExt::s_className[] = "PBVisualExt";

LPCTSTR CVisualExt::GetWindowClassName()
{
  return s_className;
}

HWND CVisualExt::CreateControl
(
  DWORD dwExStyle,      // extended window style
  LPCTSTR lpWindowName, // window name
  DWORD dwStyle,        // window style
  int x,               // horizontal position of window
  int y,            // vertical position of window
  int nWidth,       // window width
  int nHeight,      // window height
  HWND hWndParent,  // handle of parent or owner window
  HINSTANCE hInstance // handle of application instance
)
{
  d_hwnd = CreateWindowEx(dwExStyle, s_className,
    lpWindowName, dwStyle, x, y, nWidth, nHeight,
    hWndParent, NULL, hInstance, NULL);

  ::SetWindowLong(d_hwnd, GWL_USERDATA, (LONG)this);

  return d_hwnd;
}

int CVisualExt::GetEventID(
   HWND    hWnd,     /* Handle of parent window */
   UINT    iMsg,     /* Message sent to parent window*/
   WPARAM  wParam,   /* Word parameter of message*/
   LPARAM  lParam    /* Long parameter of message*/
  )
{
  if (iMsg == WM_COMMAND)
  {
    if ((HWND)lParam == d_hwnd)
    {
      switch(HIWORD(wParam))
      {
      case BN_CLICKED:
        return PB_BNCLICKED;
        break;
      }
    }
  }

  if (iMsg == WM_NOTIFY)
  {
    return PB_ENCHANGE;
  }
  return PB_NULL;
}