Painting in Progress frames

Sometimes you may want to do some extra painting on a Progress frame or window, for example some circles, dotted lines, dashed area's or just lines that are not horizontal or vertical. Or how about right-aligned text?
The actual drawing can be done with GDI functions, but the result will be erased whenever Progress repaints the frame (or window). This topic is about preventing that.
Let's start with a simple example: drawing an ellipse on a Progress frame.
create a Progress window (doesn't have to be Smart) and place a button on it. On choose of this button: run Paint.

 
PROCEDURE PAINT :
/*----------------------------------------------------
  Purpose:     do some custom painting, in this case:
               draw a circle as large as the window
------------------------------------------------------ */
  DEFINE VARIABLE hdc AS INTEGER NO-UNDO.
  DEFINE VARIABLE Okay AS INTEGER NO-UNDO.
 
  RUN GetDC IN hpApi (INPUT FRAME {&frame-name}:HWND, 
                      OUTPUT hdc).
 
  RUN Ellipse IN hpApi (hdc, 
                        0,
                        0, 
                        FRAME {&frame-name}:WIDTH-PIXELS, 
                        FRAME {&frame-name}:HEIGHT-PIXELS,
                        OUTPUT Okay ).
 
  RUN ReleaseDC IN hpApi (INPUT FRAME {&frame-name}:HWND, 
                          INPUT hdc, 
                          OUTPUT Okay).
 
END PROCEDURE.

Now when you run the window and press the button you will see a large ellipse. When you drag any other window over the surface of your window, you will see that overlapped regions of the ellipse will be erased. To repaint the ellipse you can just press the button again, but you want a way to do this automatically.

When should you repaint your drawings?

A (region of a) window will be painted again when it has been overlapped by another window, or whenever the (region of the) window has become invalidated. MS-Windows sends a series of messages to the window when it is invalidated and Progress responds to it by painting the region again. Among those messages are WM_ERASEBKGND and WM_PAINT.
I have always believed that WM_PAINT was the proper message to wait for, but there are occasions when you see that progress repaints the window without ever having trapped a WM_PAINT message. Result: your custom drawing is erased.
Matt Gilarde at PSC Development explains what's going on:
Progress doesn't repaint a frame when it gets a WM_PAINT message; we do it when we get a WM_ERASEBKGND. Why? I believe the idea was to avoid flashing during repaints. Instead of wiping out the background in the WM_ERASEBKGND and then repainting widgets in the WM_PAINT, Progress does all the painting during WM_ERASEBKGND. Painting a frame consists of the following steps:
* Fill the frame with the background color
* Draw the grid if it is on
* Paint rectangles and images
* Paint all other widgets
* Highlight selected widgets
Since the painting is handled in WM_ERASEBKGND, the WM_PAINT message is not always generated (Windows removes the WM_PAINT from the message queue if there is no invalid region to be painted). So you may have better luck trapping WM_ERASEBKGND. Or you may run into other problems.
So you will have to set up your MessageBlaster to trap WM_ERASEBKGND instead WM_PAINT.

Let's do it:

Drop a MessageBlaster ActiveX control on your window and set it up as follows:

PROCEDURE CtrlFrame.Msgblst32.MESSAGE .
 
DEFINE INPUT        PARAMETER p-MsgVal    AS INTEGER NO-UNDO.
DEFINE INPUT        PARAMETER p-wParam    AS INTEGER NO-UNDO.
DEFINE INPUT        PARAMETER p-lParam    AS INTEGER NO-UNDO.
DEFINE INPUT-OUTPUT PARAMETER p-lplRetVal AS INTEGER NO-UNDO.
 
CASE p-MsgVal :
  WHEN 20 /* = WM_ERASEBKGND */ THEN RUN paint.
END CASE.
 
END PROCEDURE.
 
 
PROCEDURE initialize-controls :
/*------------------------------------------------------------------------------
  Purpose:     listen for WM_ERASEBKGND messages
------------------------------------------------------------------------------*/
  chCtrlFrame:Msgblst32:MsgList(0)    = 20.    /* = WM_ERASEBKGND */
  chCtrlFrame:Msgblst32:MsgPassage(0) = -1.    /* = let PSC handle the message first */
  chCtrlFrame:Msgblst32:hWndTarget    = FRAME {&frame-name}:HWND.
 
END PROCEDURE.

How to force a repaint?

If you want Progress to repaint the entire frame (or window), for example to erase your custom painting, you can not just send it a WM_ERASEBKGND message. Here's another excellent explanation from Matt Gilarde:
Sending WM_ERASEBKGND won't cause any repainting to be done since it requires the wParam to be the handle to a device context for the area which is to be repainted. To force a repaint, you need to invalidate all or part of the window or frame. Windows will then generate the proper WM_ERASEBKGND and WM_PAINT messages. You can use the InvalidateRect() API to invalidate a window.

BOOL InvalidateRect(HWND hWnd, RECT *lpRect, BOOL bErase); 

Calling InvalidateRect(hWnd, 0, 1) will force the entire window to repaint. You can call UpdateWindow(hWnd) to force the repaint to occur immediately (otherwise the paint messages will sit in the message queue until Progress gets around to looking for them). The result may not be what you want, however, since there may be lots of flashing when you force the repaint.