Hello José,
With the last version of afxnova, my application dont work !
The graphs are not draw.
You gave me an example and it dont work to :
Here :
' ########################################################################################
' Microsoft Windows
' File: DDT_AdjustableArrowCapGetHeight.bas
' Contents: GDI+ - AdjustableArrowCapGetHeight example
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2025 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
#INCLUDE ONCE "AfxNova/CGdiPlus.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
#INCLUDE ONCE "AfxNova/DDT.inc"
#INCLUDE ONCE "AfxNova/AfxExt.bi" ' // optional: For dark mode
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 DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
' ========================================================================================
' The following example creates an AdjustableArrowCap, myArrow, and sets the height of the
' cap. The code then creates a Pen, assigns myArrow as the ending line cap for the Pen,
' and draws a capped line. Next, the code gets the height of the arrow cap, creates a new
' arrow cap with height equal to the height of myArrow, assigns the new arrow cap as the
' ending line cap for the Pen, and draws another capped line.
' ========================================================================================
SUB Example_GetHeight (BYVAL hdc AS HDC)
' // Create a graphics object from the window device context
DIM graphics AS CGpGraphics = hdc
' // Get the DPI scaling ratios
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
DIM ryRatio AS SINGLE = graphics.GetDpiY / 96
' // Set the scale transform
graphics.ScaleTransform(rxRatio, ryRatio)
' // Create an AdjustableArrowCap with a height of 10 pixels
DIM myArrow AS CGpAdjustableArrowCap = CGpAdjustableArrowCap(10, 10, TRUE)
' // Adjust to DPI by setting the scale width
myArrow.SetWidthScale(rxRatio)
' // Create a Pen, and assign myArrow as the end cap
DIM arrowPen AS CGpPen = ARGB_Violet
arrowPen.SetCustomEndCap(@myArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 20, 100, 20)
' // Create a second arrow cap using the height of the first one
DIM AS CGpAdjustableArrowCap otherArrow = CGpAdjustableArrowCap(myArrow.GetHeight, 20, TRUE)
otherArrow.SetWidthScale(rxRatio)
' // Assign the new arrow cap as the end cap for arrowPen
arrowPen.SetCustomEndCap(@otherArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 55, 100, 55)
END SUB
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL pwszCmdLine AS WSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // The recommended way is to use a manifest.
' // Optional: Set process DPI aware.
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Optional: Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Create a new custom dialog using pixels
' DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 400, 250, WS_OVERLAPPEDWINDOW OR DS_CENTER)
' // Alternate way: Provides an exact control of the client size
DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 0, 0, WS_OVERLAPPEDWINDOW)
DialogSetClient(hDlg, 400, 250)
DialogCenter(hDlg)
' // Optional: Caption dark mode
AfxEnableDarkModeForWindow(hDlg)
' // Add a graphic control
DIM nWidth AS LONG = DialogGetClientWidth(hDlg)
DIM nHeight AS LONG = DialogGetClientHeight(hDlg)
DIM pGraphCtx AS CGraphCtx = CGraphCtx(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' // Make the control resizable
pGraphCtx.Resizable = TRUE
' // Optional: Clear the graphic control background
pGraphCtx.Clear RGB_FLORALWHITE
' // Get the memory device context of the graphic control
DIM hdc AS HDC = pGraphCtx.GetMemDc
' // Pointer version:
' DIM pGraphCtx AS CGraphCtx = NEW CGraphCtx(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' --or--
' DIM pGraphCtx AS CGraphCtx PTR = ControlAddGraphic(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' pGraphCtx->Resizable = TRUE
' pGraphCtx->Clear RGB_FLORALWHITE
' DIM hdc AS HDC = pGraphCtx->GetMemDc
' // Draw the graphics
Example_GetHeight(hdc)
' // Display and activate the dialog as modal
DialogShowModal(hDlg, @DlgProc)
' // Pointer version: Delete the graphic control
' IF pGraphCtx THEN Delete pGraphCtx
' // Return the result code set with DialogEnd
RETURN DialogEndResult(hDlg)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Dialog callback procedure
' ========================================================================================
FUNCTION DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
SELECT CASE uMsg
CASE WM_INITDIALOG
RETURN TRUE
' // Optional: Theme has changed
' CASE WM_THEMECHANGED
' AfxEnableDarkModeForWindow(hDlg)
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 hDlg, WM_CLOSE, 0, 0
END IF
END SELECT
CASE WM_CLOSE
' // End the application
DialogEnd(hDlg)
END SELECT
END FUNCTION
' ========================================================================================
No error message, just graph are not visible...
Any help ?
Sorry, but I have been unable to get the graphic control to work properly with dialogs, so it is not longer supported. Resizing and stretching don't work properly, and the dialog refuses to display scroll bars. I have tried many things, even asking AI, but nothing has worked. Dialogs have many quirks with custom controls and, being a black box, it is not possible to know what they are doing internally. With CWindow everything works fine.
This graphx example IS working for me.. modified a little thats all..resizing works but Stretching I have No Idea.. I am using in General only SDK Modus winapi.
' ########################################################################################
' Microsoft Windows
' File: DDT_AdjustableArrowCapGetHeight.bas
' Contents: GDI+ - AdjustableArrowCapGetHeight example
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2025 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.
' ########################################################################################
'' 05-09-2025, frank bruebach, tiko, graphs are working here, scrollbar too
#define UNICODE
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "AfxNova/CGdiPlus.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
#INCLUDE ONCE "AfxNova/DDT.inc"
#INCLUDE ONCE "AfxNova/AfxExt.bi" ' // optional: For dark mode
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 DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
' ========================================================================================
' The following example creates an AdjustableArrowCap, myArrow, and sets the height of the
' cap. The code then creates a Pen, assigns myArrow as the ending line cap for the Pen,
' and draws a capped line. Next, the code gets the height of the arrow cap, creates a new
' arrow cap with height equal to the height of myArrow, assigns the new arrow cap as the
' ending line cap for the Pen, and draws another capped line.
' ========================================================================================
SUB Example_GetHeight (BYVAL hdc AS HDC)
' // Create a graphics object from the window device context
DIM graphics AS CGpGraphics = hdc
' // Get the DPI scaling ratios
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
DIM ryRatio AS SINGLE = graphics.GetDpiY / 96
' // Set the scale transform
graphics.ScaleTransform(rxRatio, ryRatio)
' // Create an AdjustableArrowCap with a height of 10 pixels
DIM myArrow AS CGpAdjustableArrowCap = CGpAdjustableArrowCap(10, 10, TRUE)
' // Adjust to DPI by setting the scale width
myArrow.SetWidthScale(rxRatio)
' // Create a Pen, and assign myArrow as the end cap
DIM arrowPen AS CGpPen = ARGB_BLUE 'ARGB_Violet
DIM arrowPen2 AS CGpPen = ARGB_GREEN 'ARGB_Violet
arrowPen.SetCustomEndCap(@myArrow)
arrowPen2.SetCustomEndCap(@myArrow)
' // Draw a line using arrowPen
'graphics.DrawLine(@arrowPen, 0, 20, 100, 20)
graphics.DrawLine(@arrowPen, 10, 40, 120, 40)
graphics.DrawLine(@arrowPen2, 40, 40, 120, 40)
' // Create a second arrow cap using the height of the first one
DIM AS CGpAdjustableArrowCap otherArrow = CGpAdjustableArrowCap(myArrow.GetHeight, 20, TRUE)
otherArrow.SetWidthScale(rxRatio)
' // Assign the new arrow cap as the end cap for arrowPen
arrowPen.SetCustomEndCap(@otherArrow)
arrowPen2.SetCustomEndCap(@otherArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 55, 100, 55)
graphics.DrawLine(@arrowPen2, 0, 85, 120, 85)
END SUB
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL pwszCmdLine AS WSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // The recommended way is to use a manifest.
' // Optional: Set process DPI aware.
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Optional: Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Create a new custom dialog using pixels
' DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 400, 250, WS_OVERLAPPEDWINDOW OR DS_CENTER)
' // Alternate way: Provides an exact control of the client size
DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 0, 0, WS_OVERLAPPEDWINDOW)
DialogSetClient(hDlg, 450, 280)
DialogCenter(hDlg)
' // Optional: Caption dark mode
AfxEnableDarkModeForWindow(hDlg)
' // Make the dialog scrollable
DialogSetViewPort(hDlg, 450, 260) '250, 260)
' // Center the window (must be done after shrinking the client size)
'DialogCenter(hDlg)
' // Add a graphic control
DIM nWidth AS LONG = DialogGetClientWidth(hDlg)
DIM nHeight AS LONG = DialogGetClientHeight(hDlg)
DIM pGraphCtx AS CGraphCtx = CGraphCtx(hDlg, IDC_GRCTX, "", 50, 50, nWidth, nHeight) '0, 0, nWidth, nHeight)
' // Make the control resizable
pGraphCtx.Resizable = TRUE
' // Optional: Clear the graphic control background
pGraphCtx.Clear RGB_FLORALWHITE
' // Get the memory device context of the graphic control
DIM hdc AS HDC = pGraphCtx.GetMemDc
' // Pointer version:
' DIM pGraphCtx AS CGraphCtx = NEW CGraphCtx(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' --or--
' DIM pGraphCtx AS CGraphCtx PTR = ControlAddGraphic(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' pGraphCtx->Resizable = TRUE
' pGraphCtx->Clear RGB_FLORALWHITE
' DIM hdc AS HDC = pGraphCtx->GetMemDc
' // Draw the graphics
Example_GetHeight(hdc)
' // Display and activate the dialog as modal
DialogShowModal(hDlg, @DlgProc)
' // Pointer version: Delete the graphic control
' IF pGraphCtx THEN Delete pGraphCtx
' // Return the result code set with DialogEnd
RETURN DialogEndResult(hDlg)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Dialog callback procedure
' ========================================================================================
FUNCTION DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
SELECT CASE uMsg
CASE WM_INITDIALOG
RETURN TRUE
' // Optional: Theme has changed
' CASE WM_THEMECHANGED
' AfxEnableDarkModeForWindow(hDlg)
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 hDlg, WM_CLOSE, 0, 0
END IF
END SELECT
CASE WM_CLOSE
' // End the application
DialogEnd(hDlg)
END SELECT
END FUNCTION
' ========================================================================================
' 05-09-2025, frank bruebach, tiko
And I add a scroll bar for x and y direction
Regards , Frank
Hello Franck,
Your code dont work for me with latest version AFXNova : i have an empty window.
Ill have to rework my apps : switch to window and abandon DDT mode.
Many work and the window syntax is less intuitive.
But José try and do his best.
AFXNova is a good lib.
Frank is making the dialog scrollable, not the graphic control.
The best thing about SDK windows is that Windows does what you tell it to do, but dialogs interfere. They weren't designed to work as main windows.
The latest change that i have made to the graphic control is to get the scroll bars thumb propertly sized relative to the size of the virtual buffer.
It's what you get used to. The syntax is the least of it. You get used to it quickly. What does change is that messages are processed a little differently. However, I have added and continue to add features that make the job easier. For example, with the control anchoring system, you don't have to worry about processing WM_SIZE. It's very easy to subclass using SetControlSubclass, and a couple of days ago I added SetControlCallback, which allows you to redirect WM_COMMAND and WM_NOTIFY messages to the designated callback procedure, as DDT allows with CALL CALLBACK.
Hello Jose . How did you make the graphic Control scrollable?
Anchor the control: pWindow.AnchorControl(IDC_GRCTX, AFX_ANCHOR_HEIGHT_WIDTH)
' ########################################################################################
' Microsoft Windows
' File: AdjustableArrowCapGetHeight.bas
' Contents: GDI+ - AdjustableArrowCapGetHeight example
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2025 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
#INCLUDE ONCE "AfxNova/CGdiPlus.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
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
' ========================================================================================
' The following example creates an AdjustableArrowCap, myArrow, and sets the height of the
' cap. The code then creates a Pen, assigns myArrow as the ending line cap for the Pen,
' and draws a capped line. Next, the code gets the height of the arrow cap, creates a new
' arrow cap with height equal to the height of myArrow, assigns the new arrow cap as the
' ending line cap for the Pen, and draws another capped line.
' ========================================================================================
SUB Example_GetHeight (BYVAL hdc AS HDC)
' // Create a graphics object from the window device context
DIM graphics AS CGpGraphics = hdc
' // Get the DPI scaling ratios
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
DIM ryRatio AS SINGLE = graphics.GetDpiY / 96
' // Set the scale transform
graphics.ScaleTransform(rxRatio, ryRatio)
' // Create an AdjustableArrowCap with a height of 10 pixels
DIM myArrow AS CGpAdjustableArrowCap = CGpAdjustableArrowCap(10, 10, TRUE)
' // Adjust to DPI by setting the scale width
myArrow.SetWidthScale(rxRatio)
' // Create a Pen, and assign myArrow as the end cap
DIM arrowPen AS CGpPen = ARGB_Violet
arrowPen.SetCustomEndCap(@myArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 20, 100, 20)
' // Create a second arrow cap using the height of the first one
DIM AS CGpAdjustableArrowCap otherArrow = CGpAdjustableArrowCap(myArrow.GetHeight, 20, TRUE)
otherArrow.SetWidthScale(rxRatio)
' // Assign the new arrow cap as the end cap for arrowPen
arrowPen.SetCustomEndCap(@otherArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 55, 100, 55)
END SUB
' ========================================================================================
' ========================================================================================
' 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
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Create the main window
DIM pWindow AS CWindow = "MyClassName"
pWindow.Create(NULL, "GDI+ AdjustableArrowCapGetHeight", @WndProc)
' // Size it by setting the wanted width and height of its client area
pWindow.SetClientSize(400, 250)
' // Center the window
pWindow.Center
' // Add a graphic control
DIM pGraphCtx AS CGraphCtx = CGraphCtx(@pWindow, IDC_GRCTX, "", 0, 0, pWindow.ClientWidth, pWindow.ClientHeight)
' // Make it smaller that the client size to no confuse it with the main window
' DIM pGraphCtx AS CGraphCtx = CGraphCtx(@pWindow, IDC_GRCTX, "", 0, 0, pWindow.ClientWidth - 20, pWindow.ClientHeight- 20)
pGraphCtx.Clear RGB_FLORALWHITE
' // Get the memory device context of the graphic control
DIM hdc AS HDC = pGraphCtx.GetMemDc
' // Anchor the control
pWindow.AnchorControl(IDC_GRCTX, AFX_ANCHOR_HEIGHT_WIDTH)
' // Draw the graphics
Example_GetHeight(hdc)
' // Displays the window and dispatches the Windows messages
FUNCTION = pWindow.DoEvents(nCmdShow)
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 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
RETURN 0
END IF
END SELECT
CASE WM_DESTROY
' // Ends the application by sending a WM_QUIT message
PostQuitMessage(0)
RETURN 0
END SELECT
' // Default processing of Windows messages
FUNCTION = DefWindowProcW(hwnd, uMsg, wParam, lParam)
END FUNCTION
' ========================================================================================
hello jose, thanks for your last example, but there's missing in winmain() part the scrollable graphctx control
this works however...
...
' // Create the main window
DIM pWindow AS CWindow = "MyClassName"
pWindow.Create(NULL, "GDI+ AdjustableArrowCapGetHeight", @WndProc)
' // Size it by setting the wanted width and height of its client area
pWindow.SetClientSize(500, 350)
' // Make the window scrollable '----> you have forgotten to make the control scollable ;)
pWindow.SetViewPort(420, 320)
' // Center the window
pWindow.Center
second part..
it was just an experiment again with DDT..
looks at my part function DlgProc() below... CASE WM_INITDIALOG, CASE WM_VSCROLL, WM_HSCROLL
perhaps you have an idea to manage and building the scrollable graphctx control...
if I am maximizing the dialog window there is a scrollbar in horizontal way for the graphctx control, not bad isn't it?
regards, frank
' ########################################################################################
' Microsoft Windows
' File: DDT_AdjustableArrowCapGetHeight.bas
' Contents: GDI+ - AdjustableArrowCapGetHeight example
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2025 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.
' ########################################################################################
'' 06-09-2025, frank bruebach, tiko, experiment ddt graphx control scrollbar
#define UNICODE
#define _WIN32_WINNT &h0602
#INCLUDE ONCE "AfxNova/CGdiPlus.inc"
#INCLUDE ONCE "AfxNova/CGraphCtx.inc"
#INCLUDE ONCE "AfxNova/DDT.inc"
#INCLUDE ONCE "AfxNova/AfxExt.bi" ' // optional: For dark mode
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 DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
' ========================================================================================
' The following example creates an AdjustableArrowCap, myArrow, and sets the height of the
' cap. The code then creates a Pen, assigns myArrow as the ending line cap for the Pen,
' and draws a capped line. Next, the code gets the height of the arrow cap, creates a new
' arrow cap with height equal to the height of myArrow, assigns the new arrow cap as the
' ending line cap for the Pen, and draws another capped line.
' ========================================================================================
SUB Example_GetHeight (BYVAL hdc AS HDC)
' // Create a graphics object from the window device context
DIM graphics AS CGpGraphics = hdc
' // Get the DPI scaling ratios
DIM rxRatio AS SINGLE = graphics.GetDpiX / 96
DIM ryRatio AS SINGLE = graphics.GetDpiY / 96
' // Set the scale transform
graphics.ScaleTransform(rxRatio, ryRatio)
' // Create an AdjustableArrowCap with a height of 10 pixels
DIM myArrow AS CGpAdjustableArrowCap = CGpAdjustableArrowCap(10, 10, TRUE)
' // Adjust to DPI by setting the scale width
myArrow.SetWidthScale(rxRatio)
' // Create a Pen, and assign myArrow as the end cap
DIM arrowPen AS CGpPen = ARGB_BLUE 'ARGB_Violet
DIM arrowPen2 AS CGpPen = ARGB_GREEN 'ARGB_Violet
arrowPen.SetCustomEndCap(@myArrow)
arrowPen2.SetCustomEndCap(@myArrow)
' // Draw a line using arrowPen
'graphics.DrawLine(@arrowPen, 0, 20, 100, 20)
graphics.DrawLine(@arrowPen, 10, 40, 120, 40)
graphics.DrawLine(@arrowPen2, 40, 40, 120, 40)
' // Create a second arrow cap using the height of the first one
DIM AS CGpAdjustableArrowCap otherArrow = CGpAdjustableArrowCap(myArrow.GetHeight, 20, TRUE)
otherArrow.SetWidthScale(rxRatio)
' // Assign the new arrow cap as the end cap for arrowPen
arrowPen.SetCustomEndCap(@otherArrow)
arrowPen2.SetCustomEndCap(@otherArrow)
' // Draw a line using arrowPen
graphics.DrawLine(@arrowPen, 0, 55, 100, 55)
graphics.DrawLine(@arrowPen2, 0, 85, 120, 85)
END SUB
' ========================================================================================
' ========================================================================================
' Main
' ========================================================================================
FUNCTION wWinMain (BYVAL hInstance AS HINSTANCE, _
BYVAL hPrevInstance AS HINSTANCE, _
BYVAL pwszCmdLine AS WSTRING PTR, _
BYVAL nCmdShow AS LONG) AS LONG
' // The recommended way is to use a manifest.
' // Optional: Set process DPI aware.
SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE)
' // Optional: Enable visual styles without including a manifest file
AfxEnableVisualStyles
' // Create a new custom dialog using pixels
' DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 400, 250, WS_OVERLAPPEDWINDOW OR DS_CENTER)
' // Alternate way: Provides an exact control of the client size
DIM hDlg AS HWND = DialogNewPixels("MyClassName", 0, "DDT - GraphCtx Test", 0, 0, 0, 0, WS_OVERLAPPEDWINDOW)
DialogSetClient(hDlg, 450, 280)
DialogCenter(hDlg)
' // Optional: Caption dark mode
AfxEnableDarkModeForWindow(hDlg)
' // Make the dialog scrollable
DialogSetViewPort(hDlg, 450, 260) '250, 260)
' // Center the window (must be done after shrinking the client size)
'DialogCenter(hDlg)
' // Add a graphic control
DIM nWidth AS LONG = DialogGetClientWidth(hDlg)
DIM nHeight AS LONG = DialogGetClientHeight(hDlg)
'new
DIM pGraphCtx AS CGraphCtx = CGraphCtx(hDlg, IDC_GRCTX, "", 50, 50, nWidth, nHeight, WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLL)
' // Make the control resizable
pGraphCtx.Resizable = TRUE
' // Optional: Clear the graphic control background
pGraphCtx.Clear RGB_FLORALWHITE
' // Get the memory device context of the graphic control
DIM hdc AS HDC = pGraphCtx.GetMemDc
' // Pointer version:
' DIM pGraphCtx AS CGraphCtx = NEW CGraphCtx(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' --or--
' DIM pGraphCtx AS CGraphCtx PTR = ControlAddGraphic(hDlg, IDC_GRCTX, "", 0, 0, nWidth, nHeight)
' pGraphCtx->Resizable = TRUE
' pGraphCtx->Clear RGB_FLORALWHITE
' DIM hdc AS HDC = pGraphCtx->GetMemDc
' // Draw the graphics
Example_GetHeight(hdc)
' // Display and activate the dialog as modal
DialogShowModal(hDlg, @DlgProc)
' // Pointer version: Delete the graphic control
' IF pGraphCtx THEN Delete pGraphCtx
' // Return the result code set with DialogEnd
RETURN DialogEndResult(hDlg)
END FUNCTION
' ========================================================================================
' ========================================================================================
' Dialog callback procedure
' ========================================================================================
FUNCTION DlgProc (BYVAL hDlg AS HWND, BYVAL uMsg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LPARAM) AS INT_PTR
SELECT CASE uMsg
CASE WM_INITDIALOG
' // Optional: Theme has changed
' CASE WM_THEMECHANGED
' AfxEnableDarkModeForWindow(hDlg)
'' new -------------------------------------------------- //
DIM hGraphCtx AS HWND = GetDlgItem(hDlg, IDC_GRCTX)
SetScrollRange(hGraphCtx, SB_VERT, 0, 100, TRUE) ' Vertical scrollbar range
SetScrollRange(hGraphCtx, SB_HORZ, 0, 100, TRUE) ' Horizontal scrollbar range
SetScrollPos(hGraphCtx, SB_VERT, 0, TRUE)
SetScrollPos(hGraphCtx, SB_HORZ, 0, TRUE)
RETURN TRUE
'' new -------------------------------------------------- //
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 hDlg, WM_CLOSE, 0, 0
END IF
END SELECT
'' new -------------------------------------------------- //
CASE WM_VSCROLL, WM_HSCROLL
DIM hScroll AS HWND = GetDlgItem(hDlg, IDC_GRCTX)
IF hScroll = CAST(HWND, lParam) THEN
DIM nScrollCode AS INTEGER = LOWORD(wParam)
DIM nPos AS INTEGER = HIWORD(wParam)
DIM nBar AS INTEGER '= GET_WM_SCROLL_BAR(wParam) ' ???
STATIC yPos AS INTEGER = 0
STATIC xPos AS INTEGER = 0
SELECT CASE nScrollCode
CASE SB_LINEUP: yPos -= 5
CASE SB_LINEDOWN: yPos += 5
CASE SB_PAGEUP: yPos -= 20
CASE SB_PAGEDOWN: yPos += 20
CASE SB_THUMBTRACK: yPos = nPos
CASE SB_LEFT: xPos -= 5
CASE SB_RIGHT: xPos += 5
CASE SB_PAGELEFT: xPos -= 20
CASE SB_PAGERIGHT: xPos += 20
CASE SB_THUMBPOSITION: xPos = nPos
END SELECT
yPos = MAX(0, MIN(yPos, 100))
xPos = MAX(0, MIN(xPos, 100))
SetScrollPos(hScroll, nBar, IIF(nBar = SB_VERT, yPos, xPos), TRUE)
InvalidateRect(hScroll, NULL, TRUE)
RETURN TRUE
END IF
'' new -------------------------------------------------- //
'CASE WM_PAINT
' DIM ps AS PAINTSTRUCT
' DIM hGraphCtx AS HWND
' DIM hdc AS HDC = BeginPaint(hGraphCtx, @ps)
' ' // Get scroll positions
' DIM yPos AS INTEGER = GetScrollPos(hGraphCtx, SB_VERT)
' DIM xPos AS INTEGER = GetScrollPos(hGraphCtx, SB_HORZ)
' ' // Create a GDI+ graphics object from the HDC
' DIM graphics AS CGpGraphics = hdc
' ' // Translate the graphics object by the scroll offset
' graphics.TranslateTransform(-xPos, -yPos)
' ' // Draw your graphics here
' Example_GetHeight(hdc)
' EndPaint(hGraphCtx, @ps)
' RETURN TRUE
CASE WM_CLOSE
' // End the application
DialogEnd(hDlg)
END SELECT
END FUNCTION
' ========================================================================================
' 06-09-2025, frank bruebach, tiko
> hello jose, thanks for your last example, but there's missing in winmain() part the scrollable graphctx control
I haven't forgotten anything. By default, the graphic control is auto-scrollable automatically if you resize it (unless you set to true the Resizable or Stretchable properties). You can either resize the graphic control processing WM_SIZE and using MoveWindow or SetWindowPos) or letting CWindow to do it automatically by anchoring the control with pWindow.AnchorControl(IDC_GRCTX, AFX_ANCHOR_HEIGHT_WIDTH). SetViewPort is to make the main window (not the graphic control) scrollable. They are different things. SetViewPort is to be used if the content does not fit in the available client area of the main window.
In SDK programming, main window or top window is what you will call dialog in DDT.
Also do not confuse Resizable with Scrollable. As stated inthe documentation, "If resizable, the virtual buffer is set to the size of the control. If the control is made smaller and then bigger, part of the contents are lost. Therefore, the caller must redraw it. Resizable and stretchable are mutually exclusive."
Thanks for the scroll code, but the one used by the control is correct; otherwise, it won't work when used with CWindow. The problem is that dialogs refuse to show the scroll bars of the control. To begin with, they don't send the WM_SIZE message to the control, but even when processing WM_SIZE and forwarding the message to the control and even using ShowScrollbar, they aren't displayed. Dialogs work fine with standard Windows controls, but with custom controls there are always problems.