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 implemented 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
I have started to write headers to add support for Direct2D in AfxNova. One of the main goals was to allow to use this technology with my graphic control, that now supports GDI, GDI+, OpenGL (classic) and Direct2D. I will also write headers for Direct Write and WIC (Windows Image Component).
This is a preliminary test drawing a circle, using Direct2D, in an instance of the grapic control.
' ########################################################################################
' Microsoft Windows
' Contents: D2D1 - Draw ellipse
' 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.
' ########################################################################################
#INCLUDE ONCE "AfxNova/CWindow.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
#include once "AfxNova/AfxD2D1.bi"
USING AfxNova
CONST IDC_GRCTX = 1001
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
' ========================================================================================
' This function draws Direct2D content to a GDI HDC.
' hMemDC = Handle of the memory device context of the graphic control
' nWidth = Width of the virtual buffer of the graphic control
' nHeight = Height of the virtual buffer of the graphic control
' ========================================================================================
FUNCTION RenderScene (BYVAL hMemDC AS HDC, BYVAL nWidth AS LONG, BYVAL nHeight AS LONG) AS HRESULT
DIM hr AS HRESULT
DIM pFactory AS ID2D1Factory PTR ' // ID2D1Factory interface
DIM pRenderTarget AS ID2D1DCRenderTarget PTR ' // ID2D1DCRenderTarget interface
DIM pBrush AS ID2D1SolidColorBrush PTR ' // ID2D1SolidColorBrush interface
' // Create an instance of the ID2D1Factory interface
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, BYVAL NULL, pFactory)
IF hr <> S_OK THEN RETURN hr
' // Get the DPI values
DIM AS SINGLE dpix, dpiY
pFactory->GetDesktopDpi(dpiX, dpiY)
' // Poperties of the tender target
DIM props AS D2D1_RENDER_TARGET_PROPERTIES = D2D1_RenderTargetProperties ( _
D2D1_RENDER_TARGET_TYPE_DEFAULT, _
D2D1_PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), _
dpiX, dpiY, _
D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)
' // Create a DC render target.
hr = pFactory->CreateDCRenderTarget(props, pRenderTarget)
IF hr <> S_OK THEN
pFactory->Release
RETURN hr
END IF
' // Get the dimensions of the graphic control drawing area.
DIM rc AS RECT = (0, 0, nWidth, nHeight)
' // Bind the DC to the DC render target.
hr = pRenderTarget->BindDC(hMemDC, rc)
' // Create a brush.
hr = pRenderTarget->CreateSolidColorBrush(D2D1_ColorF(D2D1_Blue), BYVAL NULL, pBrush)
' // The ID2D1RenderTarget.BeginDraw method signals the start of drawing.
pRenderTarget->BeginDraw
' // The ID2D1RenderTarget.Clear method fills the entire render target with a
' // solid color. The color is given as a D2D1_COLOR_F structure.
pRenderTarget->Clear(D2D1_ColorF(D2D1_White))
' // Sample code: Draws an ellipse (replace it with your drawing operations)
DIM tEllipse AS D2D1_ELLIPSE = (D2D1_Point2F(150, 150), 100, 100)
pRenderTarget->DrawEllipse(tEllipse, cast(ID2D1Brush PTR, pBrush), 3)
' // The BeginDraw, Clear, and DrawEllipse methods all have a void return type.
' // If an error occurs during the execution of any of these methods, the error
' // is signaled through the return value of the EndDraw method.
' // The EndDraw method signals the completion of drawing for this frame.
' // All drawing operations must be placed between calls to BeginDraw and EndDraw.
hr = pRenderTarget->EndDraw
' // Clean resources
D2D1_SafeRelease(pBrush)
D2D1_SafeRelease(pRenderTarget)
D2D1_SafeRelease(pFactory)
FUNCTION = hr
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL pwszCmdLine AS WSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Initializa the COM library
CoInitialize NULL
' // Set process DPI aware
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Creates the main window
DIM pWindow AS CWindow = "MyClassName" ' Use the name you wish
DIM hWin AS HWND = pWindow.Create(NULL, "D2D1 - Draw ellipse", @WndProc)
' // Sizes it by setting the wanted width and height of its client area
pWindow.SetClientSize(300, 300)
' // Centers the window
pWindow.Center
' // Set the main window background color
pWindow.SetBackColor(RGB_WHITE)
' // Add a graphic control
DIM pGraphCtx AS CGraphCtx = CGraphCtx(@pWindow, IDC_GRCTX, "", 0, 0, pWindow.ClientWidth, pWindow.ClientHeight)
pGraphCtx.Clear RGB_FLORALWHITE
' // Anchor the control
pWindow.AnchorControl(pGraphCtx.hWindow, AFX_ANCHOR_HEIGHT_WIDTH)
' // Draw the graphics
RenderScene(pGraphCtx.GetMemDc, pGraphCtx.GetVirtualBufferWidth, pGraphCtx.GetVirtualBufferHeight)
' // Displays the window and dispatches the Windows messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Uninitialize the COM library
CoUninitialize
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
' // Sent when the user selects a command item from a menu, when a control sends a
' // notification message to its parent window, or when an accelerator keystroke is translated.
CASE WM_COMMAND
SELECT CASE CBCTL(wParam, lParam)
CASE IDCANCEL
' // If ESC key pressed, close the application by sending an WM_CLOSE message
IF CBCTLMSG(wParam, lParam) = BN_CLICKED THEN SendMessageW(hwnd, WM_CLOSE, 0, 0)
END SELECT
RETURN 0
CASE WM_DESTROY
' // End the application by sending an WM_QUIT message
PostQuitMessage(0)
RETURN 0
END SELECT
' // Default processing of Windows messages
FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
The graphic control features persistence, double buffering and automatic scrolling.
Direct2D offers higher quality and speed than GDI+, and is better suited for working with high-resolution monitors. So it seems the time has come to transition to a more advanced technology.
' ########################################################################################
' Microsoft Windows
' Contents: D2D1 - Radial gradient brush
' 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.
' ########################################################################################
#INCLUDE ONCE "AfxNova/CWindow.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
#include once "AfxNova/AfxD2D1.bi"
USING AfxNova
CONST IDC_GRCTX = 1001
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
' ========================================================================================
' This function draws Direct2D content to a GDI HDC.
' hMemDC = Handle of the memory device context of the graphic control
' nWidth = Width of the virtual buffer of the graphic control
' nHeight = Height of the virtual buffer of the graphic control
' ========================================================================================
FUNCTION RenderScene (BYVAL hMemDC AS HDC, BYVAL nWidth AS LONG, BYVAL nHeight AS LONG) AS HRESULT
DIM hr AS HRESULT
DIM pFactory AS ID2D1Factory PTR ' // ID2D1Factory interface
DIM pRenderTarget AS ID2D1DCRenderTarget PTR ' // ID2D1DCRenderTarget interface
DIM pBrush AS ID2D1SolidColorBrush PTR ' // ID2D1SolidColorBrush interface
' // Create an instance of the ID2D1Factory interface
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory, BYVAL NULL, pFactory)
IF hr <> S_OK THEN RETURN hr
' // Get the DPI values
DIM AS SINGLE dpix, dpiY
pFactory->GetDesktopDpi(dpiX, dpiY)
' // Poperties of the tender target
DIM props AS D2D1_RENDER_TARGET_PROPERTIES = D2D1_RenderTargetProperties ( _
D2D1_RENDER_TARGET_TYPE_DEFAULT, _
D2D1_PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), _
dpiX, dpiY, _
D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)
' // Create a DC render target.
hr = pFactory->CreateDCRenderTarget(props, pRenderTarget)
IF hr <> S_OK THEN
pFactory->Release
RETURN hr
END IF
' // Get the dimensions of the graphic control drawing area.
DIM rc AS RECT = (0, 0, nWidth, nHeight)
' // Bind the DC to the DC render target.
hr = pRenderTarget->BindDC(hMemDC, rc)
' // Create a brush.
hr = pRenderTarget->CreateSolidColorBrush(D2D1_ColorF(D2D1_Blue), BYVAL NULL, pBrush)
' // The ID2D1RenderTarget.BeginDraw method signals the start of drawing.
pRenderTarget->BeginDraw
' // The ID2D1RenderTarget.Clear method fills the entire render target with a
' // solid color. The color is given as a D2D1_COLOR_F structure.
pRenderTarget->Clear(D2D1_ColorF(D2D1_White))
' ============================================================
' Bézier heart with radial gradient fill + glowing border + gold outline
' ============================================================
' // 1) Radial gradient for the heart fill
DIM pFillBrush AS ID2D1RadialGradientBrush PTR
DIM pFillStops AS ID2D1GradientStopCollection PTR
DIM fillStops(3) AS D2D1_GRADIENT_STOP
fillStops(0).position = 0.0
fillStops(0).color = D2D1_ColorF(1.0, 0.95, 0.6, 1.0) ' warm highlight
fillStops(1).position = 0.33
fillStops(1).color = D2D1_ColorF(1.0, 0.6, 0.2, 1.0) ' orange
fillStops(2).position = 0.66
fillStops(2).color = D2D1_ColorF(0.4, 0.3, 1.0, 1.0) ' violet
fillStops(3).position = 1.0
fillStops(3).color = D2D1_ColorF(0.1, 0.1, 0.3, 1.0) ' deep blue
pRenderTarget->CreateGradientStopCollection (fillStops(0), 4, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, pFillStops)
DIM fillProps AS D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES
fillProps.center = D2D1_Point2F(175, 150)
fillProps.gradientOriginOffset = D2D1_Point2F(-60, -40)
fillProps.radiusX = 180
fillProps.radiusY = 120
pRenderTarget->CreateRadialGradientBrush (fillProps, BYVAL NULL, pFillStops, pFillBrush)
' // 2) Glow border (soft halo)
DIM pGlowBrush AS ID2D1RadialGradientBrush PTR
DIM pGlowStops AS ID2D1GradientStopCollection PTR
DIM glowStops(2) AS D2D1_GRADIENT_STOP
glowStops(0).position = 0.0
glowStops(0).color = D2D1_ColorF(1.0, 1.0, 0.9, 1.0) ' bright glow
glowStops(1).position = 0.6
glowStops(1).color = D2D1_ColorF(1.0, 0.6, 0.2, 0.4) ' soft orange
glowStops(2).position = 1.0
glowStops(2).color = D2D1_ColorF(0.0, 0.0, 0.0, 0.0) ' fade to transparent
pRenderTarget->CreateGradientStopCollection( _
glowStops(0), 3, _
D2D1_GAMMA_2_2, _
D2D1_EXTEND_MODE_CLAMP, _
pGlowStops)
DIM glowProps AS D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES
glowProps.center = D2D1_Point2F(175, 150)
glowProps.gradientOriginOffset = D2D1_Point2F(0, 0)
glowProps.radiusX = 210
glowProps.radiusY = 210
pRenderTarget->CreateRadialGradientBrush (glowProps, BYVAL NULL, pGlowStops, pGlowBrush)
' // 3 Gold outline (solid brush)
DIM pGoldBrush AS ID2D1SolidColorBrush PTR
pRenderTarget->CreateSolidColorBrush( _
D2D1_ColorF(1.0, 0.85, 0.2, 1.0), _ ' gold color
BYVAL NULL, pGoldBrush)
' // 4) Bézier heart geometry
DIM pGeoHeart AS ID2D1PathGeometry PTR
DIM pSink AS ID2D1GeometrySink PTR
pFactory->CreatePathGeometry(pGeoHeart)
pGeoHeart->Open(pSink)
DIM AS SINGLE topX,topY : topX = 175 : topY = 90
DIM AS SINGLE lcX,lcY : lcX = 70 : lcY = 40
DIM AS SINGLE lmX,lmY : lmX = 60 : lmY = 150
DIM AS SINGLE rcX,rcY : rcX = 280 : rcY = 40
DIM AS SINGLE rmX,rmY : rmX = 290 : rmY = 150
DIM AS SINGLE bx,by : bx = 175 : by = 240
pSink->BeginFigure(D2D1_Point2F(topX, topY), D2D1_FIGURE_BEGIN_FILLED)
DIM L AS D2D1_BEZIER_SEGMENT
L.point1 = D2D1_Point2F(lcX, lcY)
L.point2 = D2D1_Point2F(lmX, lmY)
L.point3 = D2D1_Point2F(bx, by)
pSink->AddBezier(L)
DIM R AS D2D1_BEZIER_SEGMENT
R.point1 = D2D1_Point2F(rmX, rmY)
R.point2 = D2D1_Point2F(rcX, rcY)
R.point3 = D2D1_Point2F(topX, topY)
pSink->AddBezier(R)
pSink->EndFigure(D2D1_FIGURE_END_CLOSED)
pSink->Close
pSink->Release
' ============================================================
' Metallic loop above the heart
' ============================================================
' // Metallic fill brush (radial gradient)
DIM pLoopBrush AS ID2D1RadialGradientBrush PTR
DIM pLoopStops AS ID2D1GradientStopCollection PTR
DIM loopStops(2) AS D2D1_GRADIENT_STOP
loopStops(0).position = 0.0
loopStops(0).color = D2D1_ColorF(1.0, 0.95, 0.6, 1.0) ' bright gold center
loopStops(1).position = 0.5
loopStops(1).color = D2D1_ColorF(0.9, 0.7, 0.2, 1.0) ' warm gold
loopStops(2).position = 1.0
loopStops(2).color = D2D1_ColorF(0.4, 0.3, 0.1, 1.0) ' darker gold edge
pRenderTarget->CreateGradientStopCollection (loopStops(0), 3, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, pLoopStops)
DIM loopProps AS D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES
loopProps.center = D2D1_Point2F(175, 60) ' center of the loop
loopProps.gradientOriginOffset = D2D1_Point2F(-10, -10)
loopProps.radiusX = 40
loopProps.radiusY = 40
pRenderTarget->CreateRadialGradientBrush (loopProps, BYVAL NULL, pLoopStops, pLoopBrush )
' // Loop geometry (rounded arc)
DIM pGeoLoop AS ID2D1PathGeometry PTR
DIM pSink2 AS ID2D1GeometrySink PTR
pFactory->CreatePathGeometry(pGeoLoop)
pGeoLoop->Open(pSink2)
' Loop coordinates (small arc above the heart)
DIM AS SINGLE lx1, ly1 : lx1 = 145 : ly1 = 80 ' left base
DIM AS SINGLE lx2, ly2 : lx2 = 205 : ly2 = 80 ' right base
DIM AS SINGLE cx, cy : cx = 175 : cy = 40 ' top of arc
pSink2->BeginFigure(D2D1_Point2F(lx1, ly1), D2D1_FIGURE_BEGIN_FILLED)
DIM arcseg AS D2D1_BEZIER_SEGMENT
arcseg.point1 = D2D1_Point2F(150, 30) ' left control
arcseg.point2 = D2D1_Point2F(200, 30) ' right control
arcseg.point3 = D2D1_Point2F(lx2, ly2) ' right base
pSink2->AddBezier(arcseg)
' Close the bottom of the loop
pSink2->AddLine(D2D1_Point2F(lx1, ly1))
pSink2->EndFigure(D2D1_FIGURE_END_CLOSED)
pSink2->Close
pSink2->Release
' ============================================================
' // Projected shadow for the èndant
' ============================================================
' Create shadow brush (soft black)
DIM pShadowBrush AS ID2D1SolidColorBrush PTR
pRenderTarget->CreateSolidColorBrush( _
D2D1_ColorF(0.0, 0.0, 0.0, 0.35), _ ' translucent black
BYVAL NULL, _
pShadowBrush )
' Save current transform
DIM oldTransform AS D2D1_MATRIX_3X2_F
pRenderTarget->GetTransform(oldTransform)
' Build shadow transform
DIM shadow AS D2D1_MATRIX_3X2_F
' Shadow pivot (same as pendant rotation pivot)
DIM pivotX AS SINGLE : pivotX = 175
DIM pivotY AS SINGLE : pivotY = 40
' Shadow tilt angle (stronger than pendant tilt)
DIM angle AS SINGLE
angle = -22.0 ' degrees
' // Build transform:
' 1) Move to pivot
' 2) Rotate
' 3) Squash vertically (fake blur)
' 4) Move downward
shadow = _
D2D1_MatrixTranslation(-pivotX, -pivotY) * _
D2D1_MatrixRotation(angle, D2D1_Point2F(0, 0)) * _
D2D1_MatrixScale(D2D1_SizeF(1.0, 0.35)) * _
D2D1_MatrixTranslation(pivotX, pivotY + 160)
' Apply shadow transform
pRenderTarget->SetTransform(shadow)
' // Draw shadow using the same geometries
' Heart shadow
pRenderTarget->FillGeometry(pGeoHeart, pShadowBrush)
' Loop shadow
pRenderTarget->FillGeometry(pGeoLoop, pShadowBrush)
' Chain shadow (ellipses)
DIM AS SINGLE baseY : baseY = 35
DIM AS SINGLE startX : startX = 115
DIM AS SINGLE linkWidth : linkWidth = 22
DIM AS SINGLE linkHeight : linkHeight = 10
DIM AS SINGLE spacing : spacing = 18
DIM AS INTEGER i
DIM link AS D2D1_ELLIPSE
FOR i = 0 TO 6
DIM cx AS SINGLE : cx = startX + i * spacing
DIM cy AS SINGLE : cy = baseY + 3 * SIN((i - 3) * 3.14159 / 6.0)
link.point.x = cx
link.point.y = cy
link.radiusX = linkWidth / 2.0
link.radiusY = linkHeight / 2.0
pRenderTarget->FillEllipse(link, pShadowBrush)
NEXT
' // Restore original transform
pRenderTarget->SetTransform(oldTransform)
' // Release shadow brush
IF pShadowBrush THEN pShadowBrush->Release
' ============================================================
' ======= Draw everything else =======
' ============================================================
' // Fill the heart
pRenderTarget->FillGeometry(pGeoHeart, pFillBrush)
' // Glow border (soft halo)
pRenderTarget->DrawGeometry(pGeoHeart, pGlowBrush, 14.0)
' // Gold outline (sharp edge)
pRenderTarget->DrawGeometry(pGeoHeart, pGoldBrush, 3.0)
pGeoHeart->Release
' // Draw loop (fill + gold outline)
pRenderTarget->FillGeometry(pGeoLoop, pLoopBrush)
pRenderTarget->DrawGeometry(pGeoLoop, pGoldBrush, 3.0)
pGeoLoop->Release
' // Release the loop resources
D2D1_SafeRelease(pLoopBrush)
D2D1_SafeRelease(pLoopStops)
' // Restore original transform
pRenderTarget->SetTransform(oldTransform)
' // The BeginDraw, Clear, and DrawEllipse methods all have a void return type.
' // If an error occurs during the execution of any of these methods, the error
' // is signaled through the return value of the EndDraw method.
' // The EndDraw method signals the completion of drawing for this frame.
' // All drawing operations must be placed between calls to BeginDraw and EndDraw.
hr = pRenderTarget->EndDraw
' // Clean resources
D2D1_SafeRelease(pFillBrush)
D2D1_SafeRelease(pFillStops)
D2D1_SafeRelease(pGoldBrush)
D2D1_SafeRelease(pGlowBrush)
D2D1_SafeRelease(pGlowStops)
D2D1_SafeRelease(pRenderTarget)
D2D1_SafeRelease(pFactory)
FUNCTION = hr
END FUNCTION
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL pwszCmdLine AS WSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // Initializa the COM library
CoInitialize NULL
' // Set process DPI aware
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Creates the main window
DIM pWindow AS CWindow = "MyClassName" ' Use the name you wish
DIM hWin AS HWND = pWindow.Create(NULL, "D2D1 - Radial gradient brush", @WndProc)
' // Sizes it by setting the wanted width and height of its client area
pWindow.SetClientSize(350, 300)
' // Centers the window
pWindow.Center
' // Set the main window background color
pWindow.SetBackColor(RGB_WHITE)
' // Add a graphic control
DIM pGraphCtx AS CGraphCtx = CGraphCtx(@pWindow, IDC_GRCTX, "", 0, 0, pWindow.ClientWidth, pWindow.ClientHeight)
pGraphCtx.Clear RGB_FLORALWHITE
'pGraphCtx.Stretchable = TRUE
' // Anchor the control
pWindow.AnchorControl(pGraphCtx.hWindow, AFX_ANCHOR_HEIGHT_WIDTH)
' // Draw the graphics
RenderScene(pGraphCtx.GetMemDc, pGraphCtx.GetVirtualBufferWidth, pGraphCtx.GetVirtualBufferHeight)
' // Displays the window and dispatches the Windows messages
FUNCTION = pWindow.DoEvents(nCmdShow)
' // Uninitialize the COM library
CoUninitialize
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
' // Sent when the user selects a command item from a menu, when a control sends a
' // notification message to its parent window, or when an accelerator keystroke is translated.
CASE WM_COMMAND
SELECT CASE CBCTL(wParam, lParam)
CASE IDCANCEL
' // If ESC key pressed, close the application by sending an WM_CLOSE message
IF CBCTLMSG(wParam, lParam) = BN_CLICKED THEN SendMessageW(hwnd, WM_CLOSE, 0, 0)
END SELECT
RETURN 0
CASE WM_DESTROY
' // End the application by sending an WM_QUIT message
PostQuitMessage(0)
RETURN 0
END SELECT
' // Default processing of Windows messages
FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
good day José :)
that example looks great
I recommended your work to Patrice Terrier who asked if webview2 worked with FB x64 https://www.freebasic.net/forum/viewtopic.php?p=311135
Yes, I have seen that post. Thanks very much.
Regarding Direct2D, I'm currently writing small didactic templates. I already have 66.
They're useful for me to learn how to use this technology and to test if my headers are correct. Now I'll try Direct Write and then WIC. This technology is a whole ecosystem.
The old WebBrowser control used to navigate to HTTPS sites, although modern pages rendered incorrectly due to the outdated MSHTML engine. Older HTML pages still displayed fine.
After recent Windows updates, HTTPS support has effectively been removed from the legacy Internet Explorer engine, so the WebBrowser control can no longer load secure sites.
It still works with HTTP pages and local HTML files.
This change is permanent and expected, as Microsoft has fully deprecated Internet Explorer. Anyone still relying on the old WebBrowser control should migrate to WebView2 as soon as possible.
Fortunately, AfxNova provides classes for WebView2, which is the new standard and fully supports modern web technologies.
Hi, I just update my previous Html-Help for Tiko 1.3 , which is based on your md-DOCS-folder.
enclosed are two zip containing the changes , the instructions, and Readme
and the ready-to-use AfxNova_Web.
Readme_first_if_you_like.
Have Fun !
b.r. Hans (Hajubu)
# AfxNova_Web_2026
Readme for updating CWebview2.bas to AfxNova_webview2_2026
+ Hi, just updated the 'AfxNova-WebView2' , to integrate a Help Tool for the Docs for 'AfxNova' as html.
+ taken from examples latest in AfxNova-Main\AfxNova\WebView2\ dated ..._260528_1945 sha 998d7f1 ...
- Changes
- CWebview2.inc (UserDatafolder, now possible in "User-temp\..."
- i.e. %userappdata%\\local\\temp\\...) or in ...
1. Markdown to html is done with pandoc - ( fence code rules needing an editor w. R E)
```
* i.e. 1rst: regex (```\r\n---\r\n)# -> (```\r\n\r\n---\r\n\r\n)#
* 2nd: regex (---\r\n)# -> (---\r\n\r\n)#
* and adapting "Docs" folder names i.e. Webview2 : - docs\WebWiev2 -> docs\Webview2
* { A copy of AfxNova-Main-Readme is also available in html-folder }
* Even when .md-docs are updated, the maintenance is an easy-peasy task (took me only 15 minutes)
* The converted .Md-to-.html can just exchanged inside the folder structure.
```
2. When using 'pandoc' as a batch job - actual 137 - by keeping the folder structure the same as Jose's doc-folder
```
- it may be used any Browser locally
* OR on a Webserver (if you like)
* OR also within IKO 1.3 TOOLS
* ... without or with an adapted Jose's CW_Webview2.bas
* Fitting dir_name_idx.html as page index in this structure let me use it as an offline Help
* Integrating in Tiko is no rocket science. * It works a simple tool , but it helps a lot.
* File structure : html,css only ( no java,.js no php * no need !)
```
```
3. Optional
- Compiling sample of AfxNova_WebView2_2026.bas sample
- Creating/adapting some lines of code in the AfxNovaWeb.ini-file
- Adapting some lines of the org. CW_Webviev2.bas for updated AfxNova_webview2.bas ,
- changing the pWebView2.Navigate(weburl) + start-index could be called from the app.
- i.e. Instruction are in the CW_Snippet{1,2,3}_WebView2.txt for the CW_WebView2_2026.bas.
```
4. Epilog:
```
Archive:
* The '_TestPan_AfxNovaWeb2_2026.zip' archive contains
- AfxNova_webview2_2026.bas
* The zipped Readme
{org_CW_WebView2.bas + snippets.txt(3)) + AfxNovaWeb.ini + this Readme.}
- and the AfxNova_Web as html structured folder
Info:
* As the comiled Webview2-app is to large for upload ,
attached is only the sources (*.bas,*.ini) in a zip.
The names of the ini-file :AfxNovaWeb.ini is hardcoded in the source.
* Please do not forget to adapt it to your needs at least in the ini-file.
* here the template for the ini-file :
[AfxNovaWeb]
Root="V:\_testpan\_AfxNovaWeb14\AfxNova_idx.html"
Puri="file:///"
* Comiling with Tiko and AfxNova of _260528_1945 sha 998d7f1... needs
* proj.folder : (work.tiko) new project: manifest.xml,resource.rc
.\AfxNova_WebView2_2026.bas (sample)
.\loader.js
.\WebView2Loader.dll
.\AfxNovaWeb.ini
and UserDatafolder (applicable, if default is not %TEMP%)
* and if testing at least:
.\AfxNova_WebView2_2026.exe + .\AfxNovaWeb.ini + Userdatafolder/Temp
```
Have Fun !
Thanks very much for your efforts.
I've been experimenting with WebView2, and I'm genuinely impressed. This control gives desktop applications access to the full power of a modern browser, which means we can now integrate high‑quality images, animations, graphics, audio, video, printing, and much more—directly inside a native Windows program.
What makes this especially exciting is that the integration with FreeBasic is now seamless. The heavy lifting—initialization, DPI handling, resizing, message routing, and all the low‑level details—has already been solved. From this point on, everything becomes simple experimentation: write some HTML, add a bit of CSS, drop in a JavaScript library, and watch it come alive inside your application.
WebView2 also includes a fully localized context menu with built‑in features like copy, save, zoom, rotate, and print, all without writing any additional code. And because it uses the Chromium engine, we automatically get perfect DPI scaling, smooth zooming, crisp rendering, and support for the latest web standards.
There is an enormous ecosystem of JavaScript libraries available—charts, maps, UI frameworks, image processors, PDF generators, animation engines—and every one of them becomes instantly usable from FreeBasic through WebView2. With the help of AI, generating HTML, CSS, and JavaScript becomes even easier, making it possible to prototype and integrate advanced features in minutes.
In short, WebView2 gives us superpowers.
And now that it's fully integrated into FreeBasic, the only limit is our imagination. I'll be sharing examples so everyone can explore what this control can do. I'm just beginning to learn HTML, CSS, and JavaScript myself, but even at this early stage, the possibilities already look incredible.
Hi José,
I have gotten back to updating Tiko code and am just about ready to post the version 1.3.1 update. I can grab your most recent AfxNova repository files now, or I can wait a bit longer if you'd like to add anything before I publish the Tiko update. Just let me know.
Thanks,
Paul
Hi Paul,
Everything I have uploaded is working fine as far as I know, so there is no need to delay the release of the new version of Tiko.
As you can see, I'm working to provide access to the latest technologies, such as WebView2 and Direct2D. Direct2D, DirectWrite, and WIC can already be used if you manage the object lifetimes yourself and call the methods directly. I'm currently working on wrapper classes to make them easier to use by providing constructors, automatic memory management, and overloaded methods.
I have already finished wrapping the most important interfaces, but wrapping the entire set will take time, since Direct2D is a whole ecosystem. The advantages of Direct2D are that it is faster, offers higher quality, and is fully DPI‑aware — all essential qualities for modern 4K displays.
Excellent, thanks! I will use your latest repository files. Very exciting new code you're working on! I look forward to using it when you are finished. AfxNova makes coding in FB so much easier and these new technologies will make it even better.
My next step will be to take full advantage of the WebView2 control and turn it into a true "super control".
I'm planning to write a canvas class that allows drawing anything you need: graphics, images, text, shapes, and more. The idea is to expose a classic, easy‑to‑use API on the outside, while internally generating the required HTML, CSS, and JavaScript to perform the rendering.
This approach will make it possible to build all kinds of document‑related features:
- invoices and reports
- barcodes and QR codes
- embedded images
- tables and layouts
- previews and printouts
- charts and visualizations
- even animations
Basically, anything you can do on a modern web page will be available through a simple, traditional API.
The WebView2 control also has a localized context menu and/or shortcuts that allow to save, copy, rotate images, zoom and printing.
Mi idea is working.
I'm currently developing a new graphics engine based on Microsoft WebView2, wrapped inside a custom class that exposes a clean and easy‑to‑use API.
The goal is simple: give you a powerful 2D drawing system without requiring you to learn HTML, CSS, or JavaScript
Under the hood, the engine uses an HTML5 <canvas> element and JavaScript to perform all rendering. This means you automatically benefit from:
- GPU‑accelerated drawing
- smooth rendering with no flicker
- high‑resolution text and shapes
- automatic scrolling when the canvas is larger than the control
- modern browser‑level performance
But you never have to deal with any of that complexity.
Instead, you work with a very simple and intuitive API, for example:
pCanvas->DrawText("Hello World", 100, 100, "red", 40)
pCanvas->DrawLine(50, 50, 300, 200, "blue", 4)
pCanvas->DrawCircle(200, 200, 50, "purple", 3)
No fonts to create.
No pens or brushes.
No device contexts.
No GDI or Direct2D setup.
Just straightforward drawing commands.
If you do know HTML, CSS, or JavaScript, you can extend the system even further. But if you don't, the API will give you what you need to draw text, shapes, images, and more.
In short, this project aims to provide a modern, lightweight, and extremely easy graphics language, built on top of WebView2 but completely hiding the web technologies behind a clean and friendly interface.
Good day José Roca :)
this is great stuff, I can see that creating a GUI applications using your WebView2 class will be easy, even for me, thank you ;D
Reminds me a lot of the Tauri project and what they've accomplished as an alternative to Electron.
https://v2.tauri.app/
Tauri is cross platform and written in Rust.
The idea is simple. I already have a class, CWebView2, to allow to embed an instance of WebView2 in any window. CWebView2Canvas extends CWebView2, calls the BASE constructor to create the instance of WebView2 and...
Uses this tiny html script to create a canvas:
DIM s AS STRING = $"<html>"
s += "<body style='margin:0; padding:0; overflow:auto;'>"
s += $"<canvas id='cv' width='" & STR(cvWidth) & "' height='" & STR(cvHeight) & "'></canvas>"
s += $"</body>"
s += $"</html>"
Uses this tiny JavaScript function to get the context when the web page is loaded:
DIM JS_INIT AS STRING = $"var cv, ctx;"
JS_INIT += "window.onload = function() {"
JS_INIT += " cv = document.getElementById('cv');"
JS_INIT += " ctx = cv.getContext('2d');"
JS_INIT += "};"
If the WebView2 control is ready, adds the script to the control and navigats to our tiny web page.
IF this.IsReady THEN
this.AddScriptToExecuteOnDocumentCreated(JS_INIT, NULL)
this.NavigateToString(s)
END IF
With the reference to CWebView2Canvas class we can call our wrapper functions, e.g.
DIM pCanvas AS CWebView2Canvas = CWebView2Canvas(hWin, dwsUserDataFolder)
...
...
pCanvas->DrawText("Hello World", 100, 100, "red", 40)
pCanvas->DrawLine(50, 50, 300, 200, "blue", 4)
pCanvas->DrawCircle(200, 200, 50, "purple", 3)
As WebView2 is asynchronous, the safest way is to use the CWebView2NavigationCompletedEventHandler. I provide overridable classes to implement your custom event handlers.
For the wrapper methods, I use JavaScript inside them, e.g.
' ========================================================================================
' Draws a circle.
' x, y : coordinates
' radius; Radius
' clr: The color of the pen
' nWidth : The width of the pen
' ========================================================================================
PRIVATE SUB CWebView2Canvas.DrawCircle( _
BYVAL x AS SINGLE, BYVAL y AS SINGLE, _
BYVAL radius AS SINGLE, _
BYREF clr AS STRING, _
BYVAL nWidth AS LONG = 1)
' // Make sure that the JS function exists
IF m_hasDrawCircle = FALSE THEN
DIM js AS STRING
js = _
"function drawCircle(x, y, radius, color, width) {" _
& " ctx.strokeStyle = color;" _
& " ctx.lineWidth = width;" _
& " ctx.beginPath();" _
& " ctx.arc(x, y, radius, 0, Math.PI * 2);" _
& " ctx.stroke();" _
& "}"
this.ExecuteScript(js, NULL)
m_hasDrawCircle = TRUE
END IF
' // Call the JS function
DIM js AS STRING
js = "drawCircle(" _
& STR(x) & "," & STR(y) & "," _
& STR(radius) & ",'" _
& clr & "'," & STR(nWidth) & ");"
this.ExecuteScript(js, NULL)
END SUB
' ========================================================================================
If the JavaScript function does not exist, the wrapper adds it to the web page and then calls it with ExecuteScript.
As I still don't have much expertise with JavaScript, I ask the IA to write it for me.
It is not crossplatform, because WebView2 is a control for Windows, but the size of the executable of one small example with a CWindow's GUI, the CWebView2 and CWebView2Canvas classes, the event's class, DWSTRING and the wrappers for AfxNova, weights around 67 KB, not 600. As a simple example of CWindow with a button, weights 54.5 KB, this means that my WebView2 classes add 12.5 KB.
As the javascript functions are added on demand, the size of the application will grow depending on how many different ones you use. The size of the executable of the example that produces the capture below weights 82.5 KB and it is using 14 different functions.
We are not limited to graphics functions, but we can do everything: images, video, audio... and even to use external libraries.
The embeded WebView2 control also has contextual localized menus that allow to save or copy the image, and shortcut keys. For example, you can zoom the image with Ctrl+/-, or using Ctrl and the mouswheel. Scrollbars are added automatically if needed. And Ctrl+P activates the browser printer dalog, so we have printing and print preview for free. We can generate PDF files if we choose a driver like Microsoft Print to PDF or any other that you prefer.
We can also drop images, pdf files and other objects in the control.
I have added methods to draw text that can also be used with effects to, for example, draw logos.
The canvas can now display images, loaded from disk or a URL. With automatic scrolling, zoom, save and copy, print and preview, and with my "on-demand" function system, I can create a massive library without bloat. The 64-bit executable is 81 KB.
Hi, I just updated my previous Html-Help for Tiko 1.3 , which is based on your md-DOCS-folder.
enclosed are two zip containing the changes , the instructions, and Readme
and the ready-to-use AfxNova_Web.
Readme_first_if_you_like. - Have Fun !
br. Hans (hajubu)
P.S:
ref_to_: details post May 29 - AfxNova progess (https://www.planetsquires.com/protect/forum/index.php?msg=36589)
downloads available - Testpan_AfxNova_Web_260613__3fa6207 ->
-> Planetsquires Software -> Tiko 1.3 and AfxNova Docs as integrated HelpTool (https://www.planetsquires.com/protect/forum/index.php?msg=36608)
hi, just updated available - Testpan_AfxNova_Web_260616_690f2a3->related to the Array Macros ( Jose made a commit today )
see for available upd
Updated AfxNova_Web_260616_0310 (https://www.planetsquires.com/protect/forum/index.php?msg=36608)
see also Jose repo at Github
https://github.com/JoseRoca/AfxNova (https://github.com/JoseRoca/AfxNova)
Using JavaScript Libraries from FreeBasic via WebView2: A Powerful Hybrid Technique
One of the most exciting capabilities unlocked by embedding WebView2 into a FreeBasic application is the ability to combine native code with the full power of modern JavaScript libraries. In the attached example, I demonstrate how to draw shapes, Unicode text, and even emojis by using a class derived from CWebView2 that executes JavaScript commands from the Konva.js graphics framework.
This technique is far more than a simple trick. It represents a clean, extensible, and extremely powerful architectural pattern.
Why this technique is so effective
1. You get a modern 2D graphics engine "for free"
Konva.js is a high‑performance HTML5 canvas framework designed for shapes, layers, animations, and interactive graphics. By calling its API from FreeBasic through WebView2, you instantly gain:
* smooth vector rendering
* layers and groups
* hit‑testing and interactivity
* animations and tweens
* pixel‑perfect text rendering
* full Unicode support, including emojis
All of this without writing a single line of low‑level canvas code in FreeBasic.
2. Unicode "just works" — including emojis
Because the rendering is handled by the browser engine, you automatically inherit:
* full UTF‑8 support
* complex scripts
* emoji rendering
* font fallback
* anti‑aliasing and subpixel smoothing
This eliminates the usual headaches of Unicode text in native GUI frameworks.
I have added a property called JScript to my DWSTRING class to allow the use utf-16 strings instead of utf-8.
3. Clean separation of responsibilities
The derived classes will handle:
* generating the JavaScript code
* sending it to WebView2
* managing the canvas state
* abstracting away the browser details
Meanwhile, the JavaScript library handles:
* drawing
* layout
* animation
* event handling
This keeps the FreeBasic side clean and focused.
4. No global variables, no manual cleanup
Because the event handlers are implemented as real COM objects, they:
* register themselves
* unregister themselves in the destructor
* are released automatically when the WebView2 instance is destroyed
This means the user does not need to store pointers, call Delete, or manage lifetimes manually. The system is robust and self‑contained.
What else can we do with this technique?
The beauty of this architecture is that Konva.js is only the beginning. Any JavaScript library that runs in a browser can be controlled from FreeBasic simply by deriving a new class and exposing methods that generate the appropriate JavaScript.
Here are some possibilities.
1. Charts and data visualization
You can create a CWebView2Charts class that wraps libraries such as:
Chart.js
ECharts
D3.js
Plotly
ApexCharts
This would allow FreeBasic applications to display:
* bar charts
* line charts
* pie charts
* heatmaps
* real‑time data visualizations
* interactive dashboards
All with modern styling and smooth animations.
2. UI components and widgets
By wrapping libraries like:
* Bootstrap
* Material UI
* jQuery UI
you could embed:
* dialogs
* sliders
* tabs
* accordions
* responsive layouts
directly inside a FreeBasic application.
3. Maps and geospatial visualization
Using libraries such as:
* Leaflet
* Mapbox GL
* OpenLayers
you could display:
* interactive maps
* markers
* routes
* heatmaps
* GPS data
all controlled from FreeBasic.
4. Game‑style rendering
With libraries like:
* PixiJS
* Phaser
you could build:
* 2D games
* simulations
* particle effects
* sprite animations
inside a native FreeBasic window.
A flexible, extensible architecture
The key idea is simple:
Derive a class from CWebView2, and implement methods that generate JavaScript for the library you want to use.
Each derived class becomes a bridge between FreeBasic and a modern JavaScript framework.
This approach gives FreeBasic developers access to:
* modern graphics
* interactive UI components
* advanced visualization
* high‑level animation
* rich text and Unicode
* High DPI rendering consistency
All without rewriting existing libraries or reinventing the wheel.
Conclusion
By combining FreeBasic with WebView2 and JavaScript libraries like Konva.js, we unlock a hybrid development model that is both powerful and elegant. Native code handles performance‑critical logic, while JavaScript libraries provide modern rendering and UI capabilities.
This technique is not only practical — it is extensible. New derived classes can wrap any JavaScript framework, enabling FreeBasic applications to incorporate charts, maps, animations, widgets, and much more.
It's a modern approach that dramatically expands what FreeBasic applications can do.
P.S. I have added automatic scrollbars to the example.
In the example attached in the previous post, clicking the mouse right button provides a localized menu with options to save or copy the content of the canvas. Ctrl+mouse wheel allows zooming, and Ctrl+P activates the Print/Preview dialog.
Wow, that looks seriously impressive!
I am just thinking out loud - how would a FreeBasic programmer get FB data into the Javascript and then from the Javascript back to FB? It would be very convenient to be able to transfer data between the languages because you could use FB for the serious data intensive work and then Javascript for the visual stuff.
This is done using JSON. WebView2 has methods to send and receive JSON strings. I already have JSON writer and reader classes that work with DWSTRING, BSTRING and DSafeArray. I'm procedeeing step by step because all this is new to me: asynchronous events, html, javascript, JSon... However, my wrapper classes will allow to use a javascript library like Konva as if it was a FreeBasic library, e.g. m_pCanvas->DrawText("text1", 10, 10, "This is a test string", 48, "Arial", "red", "transparent", 0, TRUE). Inside, the function does this:
' ========================================================================================
' Draws a text string on the Konva canvas.
' Parameters:
' id : Optional identifier for the text (case-sensitive)
' x, y : Coordinates of the text position (in pixels). Use -1, -1 to center the text.
' dwsText : The text string to display (Unicode aware)
' fontSize : Font size in pixels
' fontFamily : Font family name (e.g., "Arial", "Verdana")
' fillColor : Text fill color (HTML/CSS color string)
' strokeColor : Outline color of the text (HTML/CSS color string)
' strokeWidth : Width of the outline (in pixels)
' draggable : If TRUE, the text can be dragged with the mouse
' Description:
' Creates a new Konva.Text object and adds it to the main layer.
' If an id is provided, it is assigned to the shape so it can be
' modified later (e.g., SetShadow, MoveTo, etc.)
' Usage example:
' m_pCanvas->DrawText("txt1", 100, 150, "Hello world", 24, "Arial", "black", "transparent", 0, TRUE)
' ========================================================================================
PRIVATE SUB CWV2CanvasKonva.DrawText ( _
BYREF id AS STRING = "", _
BYVAL x AS LONG, _
BYVAL y AS LONG, _
BYREF dwsText AS DWSTRING, _
BYVAL fontSize AS LONG = 20, _
BYREF fontFamily AS STRING = "Arial", _
BYREF fillColor AS STRING = "black", _
BYREF strokeColor AS STRING = "transparent", _
BYVAL strokeWidth AS LONG = 0, _
BYVAL draggable AS BOOLEAN = FALSE)
DIM idPart AS STRING
IF id <> "" THEN idPart = "id:'" & id & "',"
DIM js AS STRING
js = _
"var txt = new Konva.Text({" _
& idPart _
& "x:0," _
& "y:0," _
& "text:" & dwsText.JScript & "," _
& "fontSize:" & STR(fontSize) & "," _
& "fontFamily:'" & fontFamily & "'," _
& "fill:'" & fillColor & "'," _
& "stroke:'" & strokeColor & "'," _
& "strokeWidth:" & STR(strokeWidth) & "," _
& "draggable:" & IIF(draggable, "true", "false") _
& "});" _
& "layer.add(txt);" _
& "var finalX = " & STR(x) & ";" _
& "var finalY = " & STR(y) & ";" _
& "var w = txt.width();" _
& "var h = txt.height();" _
& "if(finalX == -1){" _
& " finalX = (stage.width() - w) / 2;" _
& "}" _
& "if(finalY == -1){" _
& " finalY = (stage.height() - h) / 2;" _
& "}" _
& "txt.x(finalX);" _
& "txt.y(finalY);" _
& "layer.draw();"
this.ExecuteScript(js, NULL)
END SUB
' ========================================================================================
dwsText.JScript. JScript This is a property that I have added to DWSTRING to convert its content into a safe JavaScript expression for use with WebView2 ExecuteScript. You know, in the modern web browsers, all is javascript and utf-8.
' ========================================================================================
' JScript (PROPERTY)
' ------------------
' Converts the DWSTRING content into a safe JavaScript expression for use with
' WebView2 ExecuteScript.
'
' WebView2 requires UTF-8 text when passing strings to JavaScript. This property
' takes the internal UTF-8 byte sequence of the DWSTRING and generates a JS
' expression of the form:
'
' new TextDecoder('utf-8').decode(Uint8Array.from([b1,b2,b3,...]))
'
' This guarantees that any Unicode text (including accents, symbols and emojis)
' is transmitted correctly to JavaScript without escaping issues, broken quotes,
' or encoding mismatches.
'
' The user can therefore pass DWSTRING values directly to ExecuteScript without
' worrying about UTF-8 handling or manual escaping.
'
' Usage example:
' m_pWebView->ExecuteScript("console.log(" & myString.JScript & ");")
'
' Notes:
' - The returned value is valid JavaScript code, not a literal string.
' - Ideal for passing arbitrary text safely from FreeBasic to JavaScript.
' ========================================================================================
' ========================================================================================
PRIVATE PROPERTY DWSTRING.JScript () AS STRING
DWSTRING_DP("")
DIM utf8Text AS STRING = this.utf8
DIM p AS UBYTE PTR = STRPTR(utf8Text)
DIM js AS STRING = "new TextDecoder('utf-8').decode(Uint8Array.from(["
DIM L AS LONG = LEN(utf8Text)
FOR i AS LONG = 0 TO L - 1
js += STR(p[i])
IF i < L - 1 THEN js += ","
NEXT
js += "]))"
RETURN js
END PROPERTY
' ========================================================================================
hi, just updated available - Testpan_AfxNova_Web_260629_a0d4caa->related to the Webview2 ( Jose made a commit today ) (+doc 'CRegexClass.md' )
see for available updates in the Forum ...
... Updated AfxNova_Web_260629_a0d4caa (https://www.planetsquires.com/protect/forum/index.php?action=dlattach;attach=3824)
... Tiko 1.3 and AfxNova Docs as integrated HelpTool (https://www.planetsquires.com/protect/forum/index.php?msg=36608)
PS: if you are you using "AfxNova_webview2_2026.bas" as basis,
please, do not forget to adapt "AfxNovaWeb.ini".
... ReadMe_AfxNovaWeb2_2026 (https://www.planetsquires.com/protect/forum/index.php?action=dlattach;attach=3812)
see also Jose repo at Github for the latest changes
https://github.com/JoseRoca/AfxNova
Besides Konva, I'm also adding support for SVG, HTML5 and CSS. We can use HTML5 elemens like buttons, labels, images, videos, audio...
I have to start thinking of what new application that I can build in order to dive into this new world of webview based applications. Maybe some sort of stock/finance tracker... something that I'd use daily so it'd give be incentive to keep building it. I am back deep into coding Tiko, so another application would be a good distraction so that I don't burn out just coding Tiko. :-)
I don't usually write applications anymore. What I enjoy now is building tools that integrate new technologies.
WebView2 offers plenty of technical challenges, and that's exactly what makes it interesting.
The library I'm working on behaves like a synchronous, standard Win32 library — without requiring the developer to write HTML or JavaScript directly.
For events such as click, mousemove, etc., I'm working on a mechanism inside the internal CWebView2 class to register and receive them.
These events will be translated into WM_NOTIFY messages, so they can be handled just like any other native notification in a traditional Win32 application.