Client Area Painting

When setting a callback function with CSSetOnWmPaintCallback, Consoul will call back the host when it handles the WM_PAINT events for the Consoul window client area. This allows the host to paint anywhere on the Consoul window client area.

There are two moments in the paint process when Consoul calls back the host. Which means the host callback function will be called once or twice for each paint event, depending on the pwCbkMode value that was passed to CSSetOnWmPaintCallback.

Consoul paints on a memory display context before rendering it to the actual window display context. It is when it paints on the memory display context that Consoul will call the host back. While working in its callback function the host is passed the handle to the memory display context (phDC) that the Consoul rendering engine is using to draw the necessary console content.

Paint callback function

The signature of the function that the host must adhere to is the following:

VB/A 32bits

Public Function OnConsoulWmPaint( _
  ByVal phWnd As Long, _
  ByVal pwCbkMode As Integer, _
  ByVal phDC As Long, _
  ByVal lprcLinePos As Long, _
  ByVal lprcLineRect As Long, _
  ByVal lprcPaint As Long _
) As Integer

VB/A 64bits

Public Function OnConsoulWmPaint( _
  ByVal phWnd As LongPtr, _
  ByVal pwCbkMode As Integer, _
  ByVal phDC As LongPtr, _
  ByVal lprcLinePos As LongPtr, _
  ByVal lprcLineRect As LongPtr, _
  ByVal lprcPaint As LongPtr _
) As Integer

Parameters

phWnd

The handle of the Consoul window, as returned at creation time by the CSCreateLogWindow API function.

pwCbkMode

The moment at which Consoul is calling the host back, either before doing its own painting WMPAINTCBK_BEFORE (=1) or after having drawn the console lines affected by the paint event WMPAINTCBK_AFTER (=2). Only one or the other bit is set when Consoul calls back. When the WMPAINTCBK_BEFORE is set, Consoul already has cleared the background and filled it with the Consoul window backcolor. In either case, the Consoul rendering engine has already "prepared" the display context font and attributes (etc...).

phDC

The Win32 API device context handle which the callback function can use to paint on the console surface.

lprcLinePos

Pointer to a RECT structure that contains the coordinates - in console line an column, not pixels - intersecting with the update rectangle of the WM_PAINT event.

RECT member line / column
left Leftmost column
top First line
right Rightmost column
bottom Last line

lprcLineRect

Pointer to a RECT structure that maps the rectangle pointed by lprcLinePos to pixels coordinates.

lprcPaint

Pointer to a RECT structure that is the exact update rectangle that is requested in the WM_PAINT message sent to the Consoul window. This is the rcUpdate member of the PAINTSTRUCT structure sent along the WM_PAINT message.

Remarks

Column values are computed by Consoul based on the average char width of the Consoul window font (CSGetCharWidth), which may be of poor precision when the font is not a monospaced font but a proportional or irregular character width font.

Dereferencing RECT pointers

A VB/A host cannot really "dereference" pointers, but the (Win32) RECT pointers returned by Consoul in the callback function can be used to copy the data to local variables defined as RECT, where RECT is a structured type in a standard module as:

'RECT is a classic Win32 API struct that we may use anywhere
Public Type RECT
  left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type

A function like CopyRectByAddress can then copy the pointed rectangle to another address which is the address of the VB/A RECT variable:

#If Win64 Then
Private Declare PtrSafe Function apiCopyRect Lib "user32" Alias "CopyRect" (ByVal lpDestRect As LongPtr, ByVal lpSourceRect As LongPtr) As Long
#Else
Private Declare PtrSafe Function apiCopyRect Lib "user32" Alias "CopyRect" (ByVal lpDestRect As Long, ByVal lpSourceRect As Long) As Long
#End If

#If Win64 Then
Public Function CopyRectByAddress(ByVal lpDestRect As LongPtr, ByVal lpSrcRect As LongPtr) As Long
#Else
Public Function CopyRectByAddress(ByVal lpDestRect As Long, ByVal lpSrcRect As Long) As Long
#End If
  CopyRectByAddress = apiCopyRect(lpDestRect, lpSrcRect)
End Function

Example

The following function could be called from the paint callback function. CConsoul is a VB/A class that wraps the Consoul DLL API and can be found in the Consoul VB/A SDK.

This is actually the function used by AsciiPaint 2 to display the grid on the canvas.

32bits sample

Public Declare PtrSafe Function apiGetStockObject Lib "gdi32" Alias "GetStockObject" (ByVal nIndex As Long) As LongPtr
Public Declare PtrSafe Function apiSelectObject Lib "gdi32" Alias "SelectObject" (ByVal hdc As LongPtr, ByVal hObject As LongPtr) As LongPtr
Public Declare PtrSafe Function apiCreatePen Lib "gdi32" Alias "CreatePen" (ByVal nPenStyle As Long, ByVal nWidth As Long, ByVal crColor As Long) As LongPtr
Public Declare PtrSafe Function apiDeleteObject Lib "gdi32" Alias "DeleteObject" (ByVal hObject As LongPtr) As Long
Public Declare PtrSafe Function apiMoveToEx Lib "gdi32" Alias "MoveToEx" (ByVal hdc As LongPtr, ByVal x As Long, ByVal y As Long, ByVal lpPoint As LongPtr) As Long
Public Declare PtrSafe Function apiLineTo Lib "gdi32" Alias "LineTo" (ByVal hdc As LongPtr, ByVal x As Long, ByVal y As Long) As Long

Public Function OnPaintConsoleGrid( _
    ByRef poConsole As CConsoul, _
    ByVal phWnd As Long, _
    ByVal phDC As LongPtr, _
    ByVal lprcLinePos As Long, _
    ByVal lprcLineRect As Long,_
    ByVal lprcPaint As Long _
  ) As Long

  Dim rcPaint     As RECT
  Dim rcLinePos   As RECT
  Dim rcLineRect  As RECT
  Dim hOldBrush   As LongPtr
  Dim hBrush      As LongPtr
  Dim hDotPen     As LongPtr
  Dim hOldPen     As LongPtr
  
  On Error Resume Next
  
  CopyRectByAddress VarPtr(rcPaint), lprcPaint
  CopyRectByAddress VarPtr(rcLinePos), lprcLinePos
  CopyRectByAddress VarPtr(rcLineRect), lprcLineRect
  
  hBrush = apiGetStockObject(NULL_BRUSH)
  hOldBrush = apiSelectObject(phDC, hBrush)
  hDotPen = apiCreatePen(PS_DOT, 1, RGB(200, 200, 200))
  
  hOldPen = apiSelectObject(phDC, hDotPen)
  
  Dim iCol As Integer
  Dim x    As Integer
  Dim iCharHeight As Integer
  Dim iCharWidth  As Integer
  
  iCharHeight = poConsole.LineHeight
  iCharWidth = poConsole.CharWidth
  
  For iCol = 0 To rcLinePos.Right - rcLinePos.left
    x = rcLineRect.left + (iCol * iCharWidth)
    Call apiMoveToEx(phDC, x, rcPaint.Top, 0)
    Call apiLineTo(phDC, x, rcPaint.Bottom)
  Next iCol
  
  Dim iRow As Integer
  Dim y    As Integer
  Dim iCount As Integer
  If iCharHeight > 0 Then
    iCount = (rcLineRect.Bottom - rcLineRect.Top) \ iCharHeight
  End If
  For iRow = 0 To iCount
    y = rcLineRect.Top + (iRow * iCharHeight)
    Call apiMoveToEx(phDC, rcPaint.left, y, 0)
    Call apiLineTo(phDC, rcPaint.Right, y)
  Next iRow
  
  Call apiSelectObject(phDC, hOldBrush)
  Call apiSelectObject(phDC, hOldPen)
  Call apiDeleteObject(hDotPen)
  
  OnPaintConsoleGrid = 0
End Function

Remarks

Win32 API declarations for VB/A 64bits can be found in the Win32API_PtrSafe.txt file (dating back from Office 2010). They're also valid with 32bits VBA7.

Last updated: May 13 2022.