Center a window to the working area

A window is not automatically centered to the screen, but is cascaded.
This procedure takes the widget-handle of a window as input parameter and centers the window to the 'working area'.
The working area is the portion of the screen not overlapped by the taskbar. So the result will differ when you move the taskbar to either of the 4 edges of the screen.
Windows 98 and Windows 2000 support multiple monitors. The monitors share a virtual desktop but each monitor has its own working area. This procedure is improved to center the window to the monitor where it is positioned at that time. That is, if the window is positioned somewhere on the secondary monitor it will be centered to the secondary monitor too.
The window will not dynamically stay centered using this procedure: if the size of the window changes or if the size/position of the taskbar changes, you will have to run this procedure again.

 
{windows.i}
 
PROCEDURE CenterWindow :
/*------------------------------------------------------------------------------
  Purpose:     centers window to the working area.
               ("working area" is portion of screen not obscured by taskbar)
  Parameters:  winhandle : progress widget-handle of a window widget
  Notes:       
------------------------------------------------------------------------------*/
  DEFINE INPUT PARAMETER winhandle AS HANDLE NO-UNDO.
 
  IF LOOKUP(winhandle:TYPE , "window,dialox-box":U ) = 0  THEN RETURN.
 
  /* calculate coordinates and dimensions of working area */
  DEFINE VARIABLE workingleft   AS INTEGER NO-UNDO.
  DEFINE VARIABLE workingtop    AS INTEGER NO-UNDO.
  DEFINE VARIABLE workingwidth  AS INTEGER NO-UNDO.
  DEFINE VARIABLE workingheight AS INTEGER NO-UNDO.
  DEFINE VARIABLE lpWorkingRect AS MEMPTR. /* RECT structure */
  DEFINE VARIABLE ReturnValue   AS INTEGER NO-UNDO.
 
  SET-SIZE(lpWorkingRect)=4 * {&INTSIZE}.
  RUN GetWorkingArea (winhandle:HWND, lpWorkingRect).
 
  /* RECT is filled with left,top,right,bottom */
  workingleft   = get-{&INT}(lpWorkingRect,1 + 0 * {&INTSIZE}).
  workingtop    = get-{&INT}(lpWorkingRect,1 + 1 * {&INTSIZE}).
  workingwidth  = get-{&INT}(lpWorkingRect,1 + 2 * {&INTSIZE}) - workingleft.
  workingheight = get-{&INT}(lpWorkingRect,1 + 3 * {&INTSIZE}) - workingtop.
 
 
  /* calculate current coordinates and dimensions of window */
  DEFINE VARIABLE windowleft   AS INTEGER NO-UNDO.
  DEFINE VARIABLE windowtop    AS INTEGER NO-UNDO.
  DEFINE VARIABLE windowwidth  AS INTEGER NO-UNDO.
  DEFINE VARIABLE windowheight AS INTEGER NO-UNDO.
  DEFINE VARIABLE hParent AS INTEGER NO-UNDO.
  DEFINE VARIABLE lpWinRect AS MEMPTR.
 
  SET-SIZE(lpWinRect)=4 * {&INTSIZE}.
  hParent = GetParent(winhandle:HWND).
  RUN GetWindowRect IN hpApi(hParent, 
                             GET-POINTER-VALUE(lpWinRect), 
                             OUTPUT ReturnValue).
 
  windowleft   = get-{&INT}(lpWinRect,1 + 0 * {&INTSIZE}).
  windowtop    = get-{&INT}(lpWinRect,1 + 1 * {&INTSIZE}).
  windowwidth  = get-{&INT}(lpWinRect,1 + 2 * {&INTSIZE}) - windowleft.
  windowheight = get-{&INT}(lpWinRect,1 + 3 * {&INTSIZE}) - windowtop.
 
  /* calculate new x and y for window */
  windowleft = workingleft + INTEGER((workingwidth  - windowwidth ) / 2 ).
  windowtop  = workingtop  + INTEGER((workingheight - windowheight ) / 2 ).
 
  /* perhaps you should ensure that the upper-left corner of the window
     stays visible, e.g. user can reach system-menu to close the window: */
  windowleft = MAXIMUM(workingleft, windowleft).
  windowtop  = MAXIMUM(workingtop,  windowtop).
 
  /* assign these values. No need to use API: */
  ASSIGN winhandle:X = windowleft
         winhandle:Y = windowtop.
 
  /* free memory */
  SET-SIZE(lpWorkingRect) = 0.
  SET-SIZE(lpWinRect)     = 0.
 
END PROCEDURE.
 
PROCEDURE GetWorkingArea :
  DEFINE INPUT PARAMETER HWND   AS INTEGER NO-UNDO.
  DEFINE INPUT PARAMETER lpRect AS MEMPTR  NO-UNDO.
 
  DEFINE VARIABLE hMonitor AS INTEGER NO-UNDO.
  DEFINE VARIABLE lpMonitorInfo AS MEMPTR.
  DEFINE VARIABLE ReturnValue AS INTEGER NO-UNDO.
  DEFINE VARIABLE SimpleArea AS LOGICAL NO-UNDO INITIAL NO.
 
  IF NOT (RunningWindows98() OR RunningWindows2000()) THEN
     SimpleArea = YES.
  ELSE DO:
     RUN MonitorFromWindow(HWND, 2, OUTPUT hMonitor).
     IF hMonitor = 0 THEN
        SimpleArea = YES.
     ELSE DO:
        SET-SIZE(lpMonitorInfo)    = 4 + 16 + 16 + 4.
        PUT-LONG(lpMonitorInfo, 1) = GET-SIZE(lpMonitorInfo).
        RUN GetMonitorInfoA(hMonitor,
                            GET-POINTER-VALUE(lpMonitorInfo),
                            OUTPUT ReturnValue).
        IF ReturnValue = 0 THEN 
           SimpleArea = YES.
        ELSE DO:
           PUT-LONG(lpRect, 1) = GET-LONG(lpMonitorInfo, 21).
           PUT-LONG(lpRect, 5) = GET-LONG(lpMonitorInfo, 25).
           PUT-LONG(lpRect, 9) = GET-LONG(lpMonitorInfo, 29).
           PUT-LONG(lpRect,13) = GET-LONG(lpMonitorInfo, 33).
        END.
        SET-SIZE(lpMonitorInfo)    = 0.
     END.
  END.
 
  IF SimpleArea THEN 
    RUN SystemParametersInfo{&A} IN hpApi
         ( 48,  /* 48 = SPI_GETWORKAREA */
           0,
           GET-POINTER-VALUE(lpRect),
           0,
           OUTPUT ReturnValue).
 
END PROCEDURE.
 
PROCEDURE MonitorFromWindow EXTERNAL "user32" :
  DEFINE INPUT  PARAMETER HWND     AS LONG.
  DEFINE INPUT  PARAMETER dwFlags  AS LONG.
  DEFINE RETURN PARAMETER hMonitor AS LONG.
END PROCEDURE.
 
PROCEDURE GetMonitorInfoA EXTERNAL "user32" :
  DEFINE INPUT PARAMETER  hMonitor      AS LONG.
  DEFINE INPUT PARAMETER  lpMonitorInfo AS LONG.
  DEFINE RETURN PARAMETER ReturnValue   AS LONG.
END PROCEDURE.

Explanation

Can probably do with less variables but I wanted a certain degree of readability.
The procedure has to rely that the input parameter is indeed of type "window". In other procedures (in winstyle.p) I used to traverse up the GetParent chain until a window with a caption was found. That would not work for a Splash screen.
Can't use 4GL attributes to find the size of a window, because winhandle:width-pixels and winhandle:height-pixels return the dimensions of the Client area. It is convenient but confusing that winhandle:x and winhandle:y are the coordinates of the NonClient area.
procedure SystemParametersInfo is highly interesting: it can return or change many many configuration settings from the 'Control Panel'. However it only returns the working area for the primary display monitor.
Functions RunningWindows98() and RunningWindows2000() are listed on page which version of Windows is running