AfxNova progress

Started by José Roca, November 14, 2025, 11:37:56 PM

Previous topic - Next topic

docroger

Hello José,

On the docs, windows, windows gui, menu procedures, there are lines in double and maybe some mistakes with check menu and append menu :

Quote| **AppendMenu** | Appends a new item to the end of the specified menu bar, drop-down menu, submenu, or shortcut menu. |
| **CheckMenuItem** | Appends a new item to the end of the specified menu bar, drop-down menu, submenu, or shortcut menu. |
| **CheckMenuItem** | Sets the state of the specified menu item's check-mark attribute to either selected or clear. |
| **CheckMenuRadioItem** | Checks a specified menu item and makes it a radio item. |
| **CheckMenuRadioItem** | Checks a specified menu item and makes it a radio item. |
| **CreateMenu** | Creates a menu. |
| **CreatePopupMenu** | Creates a drop-down menu, submenu, or shortcut menu. |
| **CreatePopupMenu** | Creates a drop-down menu, submenu, or shortcut menu. |
....
....
| **GetMenuString** | Copies the text string of the specified menu item into the specified buffer. |
| **GetSubMenu** | Retrieves a handle to the drop-down menu or submenu activated by the specified menu item. |
| **GetSubMenu** | Retrieves a handle to the drop-down menu or submenu activated by the specified menu item. |
| **GetSystemMenu** |Enables the application to access the window menu (also known as the system menu or the control menu) for copying and modifying. |


The docs files are very useful and afxnova very good !

Thanx for hard work.

José Roca

Thanks very much. I will check it. It is a problem of copy and paste. You copy a line to keep the fomatting and change the text, and sometimes you get distracted and forget to change it. Documentation is the worst part. Nobody likes to do it, but I make the effort of documenting all my code. A documentation like the one for GDI+, with 600+ functions, is very hard to do without doing some mistakes.

José Roca

I have removed the duplicated lines. Thanks again.

José Roca

I'm wrapping now XmlLite, to have a fast parser and writer for the XML format. I'm now in the phase of the documentation.


José Roca

Static classes

A practical design choice rooted in how Win32 actually works.

Windows standard controls — BUTTON, EDIT, LISTBOX, etc. — are not object‑oriented. They are handle‑based, message‑driven window classes created by the OS.

This means:

- Windows owns the control
- You interact with it through an HWND
- All operations happen through messages and APIs
- The control can be accessed from anywhere as long as you have the handle
- Trying to wrap this model in strict OOP creates more problems than it solves.

AfxNova's static classes embrace the Win32 model instead of fighting it.

Classic OOP pain points:

- Where do I store the object?
- How do I access the control from a callback?
- How do I map HWND → object?
- What happens when the control is destroyed?"

A static class in AfxNova:

- does not need an instance
- does not track lifetime
- does not require storing objects
- works directly with the HWND
- can be called from anywhere
- matches the Win32 philosophy exactly

Example:

CButton.SetIcon(hButton, hIcon)
CButton.Enable(hButton)
CButton.SetText(hButton, "OK")

This is clean, predictable, and 100% compatible with how Windows expects you to work.

Why static classes are beneficial

Static classes let you group all related functionality in one place, instead of scattering macros and procedures across multiple files. They also allow the use of overloaded methods, making the API easier and more intuitive to use.

As a proof-of-concept, I have impremented CButton, a static class that wraps the functionality of a Button control. It is available at https://github.com/JoseRoca/AfxNova/blob/main/AfxNova/CButton.inc

This shows how a static class can group all related functionality in one place, provide overloads for ease of use, and remain fully compatible with the native Win32 handle‑based model.

These classes aren't limited to Windows controls — the same approach can be used to wrap other WinAPI technologies as well.

José Roca

With the help of my OLE Container (CAxHost), I have managed to get several of the old VB6 ActiveX controls (OCXs) to work with CWindow.

One of the most interesting is the Microsoft Hyerarchical Grid Control. It works wonderfully with CWindow, my ADO classes and the Jet Database, that comes pre-installed in Windows.

The only limitation is that it is 32-bit only.

This is one of my tests:

' ########################################################################################
' Microsoft Windows
' Contents: Embedded Microsoft Hierarchical Grid Control
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2026 José Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################

#define UNICODE
'#define _CAXH_DEBUG_ 1
#INCLUDE ONCE "AfxNova/AfxCOM.inc"
#INCLUDE ONCE "AfxNova/CAxHost.inc"
#INCLUDE ONCE "AfxNova/MSHFlexGrid.inc"
#INCLUDE ONCE "AfxNova/CADODB.inc"
USING AfxNova

DECLARE FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
                           BYVAL hPrevInstance AS HINSTANCE, _
                           BYVAL pwszCmdLine AS WSTRING PTR, _
                           BYVAL nCmdShow AS LONG) AS LONG
   END wWinMain(GetModuleHandleW(NULL), NULL, wCommand(), SW_NORMAL)

' // Forward declaration
DECLARE FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

CONST IDC_GRID = 1001

' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
                   BYVAL hPrevInstance AS HINSTANCE, _
                   BYVAL pwszCmdLine AS WSTRING PTR, _
                   BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
   ' // The recommended way is to use a manifest file
   AfxSetProcessDPIAware

   ' // Creates the main window
   DIM pWindow AS CWindow
   ' -or- DIM pWindow AS CWindow = "MyClassName" (use the name that you wish)
   DIM hwndMain AS HWND = pWindow.Create(NULL, "Microsoft Hierarchical Flex Grid", @WndProc)
   ' // Sizes it by setting the wanted width and height of its client area
   pWindow.SetClientSize(800, 450)
   ' // Centers the window
   pWindow.Center

   DIM wszLibName AS WSTRING * 260 = ExePath & "\MSHFLXGD.OCX"
   DIM pHost AS CAxHost = CAxHost(@pWindow, IDC_GRID, wszLibName, AFX_CLSID_MSHFlexGrid, _
       AFX_IID_IMSHFlexGrid, RTLKEY_MSHFlexGrid, 0, 0, pWindow.ClientWidth, pWindow.ClientHeight)
   pWindow.AnchorControl(IDC_GRID, AFX_ANCHOR_HEIGHT_WIDTH)
   SetFocus pHost.hWindow

   DIM pGrid AS CMSHFlexGrid = pHost.OcxDispObj
   ' // Set the width of the columns (in twips)
   pGrid.ColWidth(0) = 300
   pGrid.ColWidth(1) = 1100
   pGrid.ColWidth(2) = 3000
   pGrid.ColWidth(3) = 2000
   pGrid.ColWidth(4) = 2000
   pGrid.ColWidth(5) = 3000
   pGrid.ColWidth(6) = 1500
   pGrid.ColWidth(7) = 700
   pGrid.ColWidth(8) = 1200
   pGrid.ColWidth(9) = 1200
   pGrid.ColWidth(10) = 1500
   pGrid.ColWidth(11) = 1500
   ' Change the foreground and background colors
   pGrid.ForeColor = BGR(0, 0, 0)
   pGrid.BackColor = BGR(255,255,235)

   ' Open an ADO connection
   DIM pConnection AS CAdoConnection PTR = NEW CAdoConnection
   pConnection->ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & ExePath & $"\nwind.mdb"
   pConnection->Open
   ' Open a recordset
   DIM pRecordset AS CAdoRecordset PTR = NEW CAdoRecordset
   DIM dvSource AS DVARIANT = "SELECT * FROM Customers"
   pRecordset->Open(dvSource, pConnection, adOpenKeyset, adLockOptimistic, adCmdText)
   ' Set the Datasource property of the recordset
   pGrid.DataSource = cast(ANY PTR, pRecordset->DataSource)
   ' Close the recordset
   pRecordset->Close
   ' Close the connection
   pConnection->Close
   ' // Delete the recordset
   Delete pRecordset
   ' // Delete the connection
   Delete pConnection

   ' // Display the window
   ShowWindow(hwndMain, nCmdShow)
   UpdateWindow(hwndMain)

   ' // Dispatch Windows messages
   DIM uMsg AS MSG
   WHILE (GetMessageW(@uMsg, NULL, 0, 0) <> FALSE)
      IF AfxCAxHostForwardMessage(GetFocus, @uMsg) = FALSE THEN
         IF IsDialogMessageW(hwndMain, @uMsg) = 0 THEN
            TranslateMessage(@uMsg)
            DispatchMessageW(@uMsg)
         END IF
      END IF
   WEND
   FUNCTION = uMsg.wParam

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main window procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

   SELECT CASE uMsg

      ' // If an application processes this message, it should return zero to continue
      ' // creation of the window. If the application returns –1, the window is destroyed
      ' // and the CreateWindowExW function returns a NULL handle.
      CASE WM_CREATE
         AfxEnableDarkModeForWindow(hwnd)
         RETURN 0

      ' // Theme has changed
      CASE WM_THEMECHANGED
         AfxEnableDarkModeForWindow(hwnd)
         RETURN 0

      CASE WM_COMMAND
         SELECT CASE LOWORD(wParam)
            CASE IDCANCEL
               ' // If ESC key pressed, close the application by sending an WM_CLOSE message
               IF HIWORD(wParam) = BN_CLICKED THEN
                  SendMessageW hwnd, WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

    CASE WM_DESTROY
         ' // Ends the application by sending a WM_QUIT message
         PostQuitMessage(0)
         EXIT FUNCTION

   END SELECT

   ' // Default processing of Windows messages
   FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================

Notice that instead of pWindow.DoEvents I'm using a message pump:

   ' // Dispatch Windows messages
   DIM uMsg AS MSG
   WHILE (GetMessageW(@uMsg, NULL, 0, 0) <> FALSE)
      IF AfxCAxHostForwardMessage(GetFocus, @uMsg) = FALSE THEN
         IF IsDialogMessageW(hwndMain, @uMsg) = 0 THEN
            TranslateMessage(@uMsg)
            DispatchMessageW(@uMsg)
         END IF
      END IF
   WEND

In it, AfxCAxHostForwardMessage gives the hosted grid to process keyboard messages.

José Roca

With some tweaking, we can simulate the fashionable dark mode:

   ' // Scroll bars dark theme
   SetWindowTheme(pGrid.hwnd, "DarkMode_Explorer", NULL)
   ' // Change the foreground and background colors
   pGrid.ForeColor = RGB_WHITE
   pGrid.BackColor = RGB_BLACK
   ' // change the color of the background of the grid
   pGrid.BackColorBkg = RGB_BLACK
   ' // Change the colors of the fixed parts of the grid
   pGrid.ForeColorFixed = RGB_WHITE
   pGrid.BackColorFixed = BGR(90, 90, 90)
   ' // Color of the grid
   pGrid.GridColor = RGB_GRAY
   pGrid.GridLines = flexGridFlat
   pGrid.GridColorHeader = RGB_GRAY
   pGrid.GridLinesFixed = flexGridFlat