I have done much work with GDI+.
GDI+ Flat API
As the FreeBasic headers for GID+ are a mess, with different headers for 32 and 64 bit, with many naming conflicts and import libraries partially broken, I have writen my own headers: AfxGdiPlus.bi.
All the 600+ functions are documented here:
https://github.com/JoseRoca/AfxNova/tree/main/docs/Graphics%20/GdiPlus%20Flat%20%20Api
And there are hundreds of examples here:
https://github.com/JoseRoca/AfxNova/tree/main/Examples/GdiPlus%20Flat%20API
GDI+ classes
These classes replicate the C++ ones.
hey are documented here:
https://github.com/JoseRoca/AfxNova/tree/main/docs/Graphics%20/GdiPlus%20Classes
And there are hundreds of examples here:
https://github.com/JoseRoca/AfxNova/tree/main/Examples/GdiPlus%20Classes
Currently, I'm working in a class, called CWebView2, that allows to embed the WebView2 control in a FreeBasic application.
he main part is already implemented and working. There is still much work to do to write the many event's classes.
With this short code you can create an instance of WebView2 and navigate to a site.
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
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Enable visual styles without including a manifest file
AfxEnableVisualStyles
DIM pWindow AS CWindow
DIM hWin AS HWND = pWindow.Create(NULL, "WebView2", @WndProc)
pWindow.SetClientSize(1050, 500)
pWindow.Center
' DIM pWebView2 AS CWebView2 = hWin
DIM pWebView2 AS CWebView2 = CWebView2(hWin, ExePath)
IF pWebView2.IsReady(5000) THEN
' // Navigate to the web page
pWebView2.Navigate("https://www.planetsquires.com/protect/forum/index.php")
ELSE
' // Timeout
END IF
' // Dispatch Windows messages
FUNCTION = pWindow.DoEvents(nCmdShow)
END FUNCTION
Incredible work, thanks a million for this, I am amazed at the amount of work you've put into GDI+ and WebView2
It is needed to deal with the high resolution monitors. The old WebBrowser control is obsolete and we need WebView2. And regading GDI+, I have found solutions to make graphics DPI aware. And DWSTRING provides optional support to fix broken surrogate pairs and allows to use utf-8. I do the same trick that Windows uses with the "A" functions: convert utf-8 to utf-16 on input, allowing to use all the string functions and operators, and optionally convert to utf-8 on output. It only requires a constructor and two properties.
Afx needed a modernization.
The events are solved. I will provide overridable classes that the user can override like this:
' ########################################################################################
' CWebView2NavigationCompletedEventHandlerImpl class
' Implementation of the ICoreWebView2NavigationCompletedEventHandler callback interface.
' ########################################################################################
TYPE CWebView2NavigationCompletedEventHandlerImpl EXTENDS CWebView2NavigationCompletedEventHandler
' ICoreWebView2NavigationCompletedEventHandler
DECLARE FUNCTION Invoke (BYVAL sender AS Afx_ICoreWebView2 PTR, BYVAL args AS Afx_ICoreWebView2NavigationCompletedEventArgs PTR) AS HRESULT
DECLARE CONSTRUCTOR (BYVAL pWebView2 AS CWebView2 PTR)
DECLARE DESTRUCTOR
m_pWebView2 AS CWebView2 PTR
m_token AS EventRegistrationToken
END TYPE
' ########################################################################################
' ========================================================================================
CONSTRUCTOR CWebView2NavigationCompletedEventHandlerImpl (BYVAL pWebView2 AS CWebView2 PTR)
CWV2_DP("")
IF pWebView2 THEN
m_pWebView2 = pWebView2
pWebView2->AddNavigationCompleted(cast(ANY PTR, @this), @m_token)
END IF
END CONSTRUCTOR
' ========================================================================================
' ========================================================================================
DESTRUCTOR CWebView2NavigationCompletedEventHandlerImpl
CWV2_DP("")
IF m_pWebView2 THEN m_pWebView2->RemoveNavigationCompleted(m_token)
END DESTRUCTOR
' ========================================================================================
' ========================================================================================
FUNCTION CWebView2NavigationCompletedEventHandlerImpl.Invoke (BYVAL sender AS Afx_ICoreWebView2 PTR, BYVAL args AS Afx_ICoreWebView2NavigationCompletedEventArgs PTR) AS HRESULT
CWV2_DP("*** Navigation completed ***")
RETURN S_OK
END FUNCTION
' ========================================================================================
And will set as follows:
DIM pEvt AS CWebView2NavigationCompletedEventHandlerImpl PTR
pEvt = NEW CWebView2NavigationCompletedEventHandlerImpl(@pWebView2)
Now I only have to implement classes for all the events (there are many) and we will have a great control ready to use.
All the essential is done and tested. What remains is to wrap the latest interfaces and events, most of which aren't essential unless you intend to write a full web browser. What we have is a very useful control that can do all that a web browser can do: maps, charts, images, graphics, videos, audio, web pages, etc. It can be embedded in any window, the most suitable being a label control.
The attached capture shows a WebView control embedded into a label displaying a Goodle map.
I have been playing with dark mode. Here you will find templates for most of the standard Windows controls except Menus: https://github.com/JoseRoca/AfxNova/tree/main/Templates/SDK%20Templates
For easy identification, these examples have "_DarkMode" as the suffix.
I also have started to post WebView2 examples:
https://github.com/JoseRoca/AfxNova/tree/main/Examples/CWebView2
The WebView2Loader.dll that I'm using is them one for 64 bit. It you want to try 32 bit, rename WebView2Loader_32.dll as WebView2Loader.dll.
A good place where to find examples that can be adapted to work with WebView2 is WC3 Schools: https://www.w3schools.com/html/default.asp
thank you José Roca :)
HI,jose
!great work!
- dark modes are fine
- just tested the webview2 samples - worked fine. Thank's for your effort done.
----hajubu---
Just a small hint:
samples (*.bas) in utf16LE like the both "GoogleCircularChart.bas" and "Chartjs_CircularChart.bas"
are getting mixed up , when you use the the full zip download.
' ########################################################################################
�✀ 䴀椀挀爀漀猀漀昀琀 圀椀渀搀漀眀猀ഀഀ
' File: Google circular chart.bas
Download as single raw file selection keeps the UTF16LE correct format.
' ########################################################################################
' Microsoft Windows
' File: Google circular chart.bas
Is there a real need to use UTF16LE instead of UTF8 ?
(mho : Tiko is save for UTF16 and UTF8 , which is not true for the zips in Gith.)
Thanks very much. I have reuploaded them. There is not need to use UTF-16, except that I tried to see if it worked with these examples. I used Ελιές, (Olives in Greek). It worked, but forgot to set Tiko back to Ansi. Sorry for the inconvenience.
Hello José,
With last version afxnova, i cant compile cw_menu_icon_01... : compiling resource failed.
Seems icons are missing in resources dir (into templates).
Am i missing something ?
hi docroger
hi jose
yes here some icons missing - even some are doubled in DDT_Templates resources.
my fast analysis for the several SDK CW_xxx.rc files references for the SDK_Templates, especially for the menu_icon_01
home_32,home_64, save_32, save_64
just in case ...
here a shared file with the missing icons inside:
sdk_resources_save+home.7z (https://limewire.com/d/QdFUd#TiAvQeXJ4a)
Yes, some icons were missing. I have reuploaded them.
Seems that I uploaded them to the Resources folder of the DDT templates and forgot to do the same in the Resources folder of the SK Templates. I'm sorry. I've been so busy with GDI++, WebView and Dark Mode that I'm being a bit forgetful.
Microsoft is developing WebView2 very rapidly. There are 27 new interfaces and 18 new events in ICoreWebView (unless they've implemented more since my last check). Every time they add one or two methods, they implement a new interface, which inherits from the existing ones. The reason there are so many event interfaces is that WebView2 works asynchronously, so you generally don't get the result directly when you call a method, but rather in the event callbacks. Most of the new methods aren't essential, but rather refinements.
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.
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.
I have removed the duplicated lines. Thanks again.
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.
Finished the work with the CXmlLite class.
Headers
https://github.com/JoseRoca/AfxNova/blob/main/AfxNova/AfxXmlLite.bi
CXmlLite class
https://github.com/JoseRoca/AfxNova/blob/main/AfxNova/CXmlLite.inc
Documentation
https://github.com/JoseRoca/AfxNova/blob/main/docs/File%20Management%20/CXmlLite%20Class.md
Examples
https://github.com/JoseRoca/AfxNova/tree/main/Examples/XML
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.
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.
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