Using Custom Cursors
Posted: Sat May 03, 2025 10:09 am
Hello everyone
The old LoadCursorFromFile_(name) API used to load a cursor is still functional but deprecated; it has been replaced by LoadImage_(hInst,name,#type,cx,cy,#flags).
If you want to use cursors other than those provided by the system in your drawing applications (or any other application), there's a simple way to do so using PureBasic's functions.
For the usage example, I've adapted the one provided in the documentation.
For those who don't have their own cursors, I'm offering some of mine, which you can download here: MyCursors.zip
Have fun
Tested with Windows11, PB 6.21B8, x64 and x86
Example of use:
The old LoadCursorFromFile_(name) API used to load a cursor is still functional but deprecated; it has been replaced by LoadImage_(hInst,name,#type,cx,cy,#flags).
If you want to use cursors other than those provided by the system in your drawing applications (or any other application), there's a simple way to do so using PureBasic's functions.
For the usage example, I've adapted the one provided in the documentation.
For those who don't have their own cursors, I'm offering some of mine, which you can download here: MyCursors.zip
Have fun

Tested with Windows11, PB 6.21B8, x64 and x86
Example of use:
Code: Select all
;================================
; Loads an icon, cursor, animated cursor, or bitmap.
; https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadimagea#return-value
; HANDLE LoadImageA(
; [in, optional] HINSTANCE hInst, (A handle to the module in a DLL or executable (.exe) containing the image to load. To load a predefined image or a standalone resource (icon, cursor, or bitmap file), set this parameter to NULL.)
; [in] LPCSTR name, (full file name)
; [in] UINT type, (file type defined by: #IMAGE_BITMAP or #IMAGE_CURSOR or #IMAGE_ICON)
; [in] int cx, (cursor width = 32 by default)
; [in] int cy, (cursor height = 32 by default)
; [in] UINT fuLoad (#flags)
; );
; Return value : HANDLE
; - If the function succeeds, the return value is the handle of the newly loaded image.
; - If the function fails, the return value is NULL.
; --> HANDLE = LoadImage_(hInst,name,#type,cx,cy,#flags)
;-FLAGS :
; #LR_CREATEDIBSECTION - When the uType parameter specifies IMAGE_BITMAP, causes the function to return a DIB section bitmap rather than a compatible bitmap. This flag is useful for loading a bitmap without mapping it to the colors of the display device.
; #LR_DEFAULTCOLOR - The default flag; it does nothing. All it means is "not LR_MONOCHROME".
; #LR_DEFAULTSIZE - Uses the width or height specified by the system metric values for cursors or icons, if the cxDesired or cyDesired values are set to zero. If this flag is not specified and cxDesired and cyDesired are set to zero, the function uses the actual resource size. If the resource contains multiple images, the function uses the size of the first image.
; #LR_LOADFROMFILE - Loads the standalone image from the file specified by name (icon, cursor, or bitmap file).
; #LR_LOADMAP3DCOLORS - Searches the color table for the image and replaces the following shades of gray with the corresponding 3-D color. Do not use this option if you are loading a bitmap with a color depth greater than 8bpp.
; #LR_LOADTRANSPARENT - Retrieves the color value of the first pixel in the image and replaces the corresponding entry in the color table with the default window color (COLOR_WINDOW). All pixels in the image that use that entry become the default window color. This value applies only to images that have corresponding color tables. Do not use this option if you are loading a bitmap with a color depth greater than 8bpp.
; #LR_MONOCHROME - Loads the image in black and white.
; #LR_SHARED - Shares the image handle if the image is loaded multiple times. If LR_SHARED is not set, a second call to LoadImage for the same resource will load the image again and return a different handle. Do not use LR_SHARED for images that have non-standard sizes, that may change after loading, or that are loaded from a file.
; #LR_VGACOLOR - Uses true VGA colors.
; To release the used resource:
; Bitmap : DeleteObject(HGDIOBJ)
; Cursor : DestroyCursor(HCURSOR)
; Icon : DestroyIcon(HICON)
;================================
Enumeration
#IMAGE_Content ; stores the previous CanvasGadget content while the mouse is down
#IMAGE_Color
#IMAGE_LoadSave
EndEnumeration
Enumeration
#GADGET_Canvas
#GADGET_Color
#GADGET_Brush
#GADGET_Line
#GADGET_Box
#GADGET_Circle
#GADGET_Fill
#GADGET_Clear
#GADGET_Load
#GADGET_Save
EndEnumeration
;With new API
Global HCURSOR_F = LoadImage_(#Null,"MyCursors\Paint_fill.cur",#IMAGE_CURSOR,32,32,#LR_LOADFROMFILE|#LR_VGACOLOR)
; If HCURSOR_F = #Null
; Debug "Erreur lors du chargement du curseur!"
; EndIf
;With old API
Global HCURSOR_B = LoadCursorFromFile_("MyCursors\Rectangle_full.cur")
Global HCURSOR_C = LoadCursorFromFile_("MyCursors\Pencil_1.cur")
;Global HCURSOR_F = LoadCursorFromFile_("MyCursors\Paint_fill.cur")
Global HCURSOR_E = LoadCursorFromFile_("MyCursors\Ellipse_empty.cur")
Global HCURSOR_L = LoadCursorFromFile_("MyCursors\Line.cur")
Global HCURSOR_P = LoadCursorFromFile_("MyCursors\Clic_on_plus.ani") ; curseur animé
Procedure Destroy_Curseurs()
DestroyCursor_(HCURSOR_B)
DestroyCursor_(HCURSOR_C)
DestroyCursor_(HCURSOR_F)
DestroyCursor_(HCURSOR_E)
DestroyCursor_(HCURSOR_L)
DestroyCursor_(HCURSOR_P)
EndProcedure
Global CurrentColor, CurrentMode, StartX, StartY
; Draw the mouse action result depending on the currently selected mode and event type
;
Procedure DrawAction(x, y, EventType)
If StartDrawing(CanvasOutput(#GADGET_Canvas))
Select CurrentMode
Case #GADGET_Brush
If EventType = #PB_EventType_LeftButtonDown Or EventType = #PB_EventType_MouseMove
Circle(x, y, 5, CurrentColor)
EndIf
Case #GADGET_Line
DrawImage(ImageID(#IMAGE_Content), 0, 0)
LineXY(StartX, StartY, x, y, CurrentColor)
Case #GADGET_Box
DrawImage(ImageID(#IMAGE_Content), 0, 0)
Box(StartX, StartY, x-StartX, y-StartY, CurrentColor)
Case #GADGET_Circle
DrawImage(ImageID(#IMAGE_Content), 0, 0)
If x > StartX
rx = x - StartX
Else
rx = StartX - x
EndIf
If y > StartY
ry = y - StartY
Else
ry = StartY - y
EndIf
If GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Modifiers) & #PB_Canvas_Control
ry = rx
EndIf
Ellipse(StartX, StartY, rx, ry, CurrentColor)
Case #GADGET_Fill
If EventType = #PB_EventType_LeftButtonDown
FillArea(x, y, -1, CurrentColor)
EndIf
EndSelect
StopDrawing()
EndIf
EndProcedure
UseJPEGImageDecoder()
UseJPEGImageEncoder()
CurrentColor = $000000
CurrentMode = #GADGET_Brush
CreateImage(#IMAGE_Color, DesktopScaledX(35), DesktopScaledY(35), 24)
CreateImage(#IMAGE_Content, DesktopScaledX(380), DesktopScaledY(380), 24)
If OpenWindow(0, 0, 0, 460, 400, "CanvasGadget", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(#GADGET_Canvas, 10, 10, 380, 380, #PB_Canvas_ClipMouse)
ButtonImageGadget(#GADGET_Color, 400, 10, 50, 50, ImageID(#IMAGE_Color))
ButtonGadget(#GADGET_Brush, 400, 100, 50, 25, "Brush", #PB_Button_Toggle)
ButtonGadget(#GADGET_Line, 400, 130, 50, 25, "Line", #PB_Button_Toggle)
ButtonGadget(#GADGET_Box, 400, 160, 50, 25, "Box", #PB_Button_Toggle)
ButtonGadget(#GADGET_Circle, 400, 190, 50, 25, "Circle", #PB_Button_Toggle)
ButtonGadget(#GADGET_Fill, 400, 220, 50, 25, "Fill", #PB_Button_Toggle)
ButtonGadget(#GADGET_Clear, 400, 280, 50, 25, "Clear")
ButtonGadget(#GADGET_Load, 400, 335, 50, 25, "Load")
ButtonGadget(#GADGET_Save, 400, 365, 50, 25, "Save")
SetGadgetState(#GADGET_Brush, 1)
;SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Cursor, #PB_Cursor_Cross)
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_P)
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case #GADGET_Brush
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_P)
CurrentMode = EventGadget()
SetGadgetState(#GADGET_Brush, 1) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0)
Case #GADGET_Line
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_L)
CurrentMode = EventGadget()
SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 1) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0)
Case #GADGET_Box
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_B)
CurrentMode = EventGadget()
SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 1) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 0)
Case #GADGET_Circle
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_E)
CurrentMode = EventGadget()
SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 1) : SetGadgetState(#GADGET_Fill, 0)
Case #GADGET_Fill
SetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_CustomCursor, HCURSOR_F)
CurrentMode = EventGadget()
SetGadgetState(#GADGET_Brush, 0) : SetGadgetState(#GADGET_Line, 0) : SetGadgetState(#GADGET_Box, 0) : SetGadgetState(#GADGET_Circle, 0) : SetGadgetState(#GADGET_Fill, 1)
Case #GADGET_Canvas ;{
X = GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_MouseX)
Y = GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_MouseY)
Type = EventType()
Select EventType()
Case #PB_EventType_LeftButtonDown
;
; This stores the current content of the CanvasGadget in #IMAGE_Content,
; so it can be re-drawn while the mouse moves
;
If StartDrawing(ImageOutput(#IMAGE_Content))
DrawImage(GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Image), 0, 0)
StopDrawing()
EndIf
StartX = X
StartY = Y
DrawAction(X, Y, EventType())
Case #PB_EventType_LeftButtonUp
DrawAction(X, Y, EventType())
Case #PB_EventType_MouseMove
If GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Buttons) & #PB_Canvas_LeftButton
DrawAction(X, Y, EventType())
EndIf
EndSelect
;}
Case #GADGET_Color ;{
CurrentColor = ColorRequester(CurrentColor)
If StartDrawing(ImageOutput(#IMAGE_Color))
Box(0, 0, 35, 35, CurrentColor)
StopDrawing()
SetGadgetAttribute(#GADGET_Color, #PB_Button_Image, ImageID(#IMAGE_Color))
EndIf
;}
Case #GADGET_Clear ;{
If StartDrawing(CanvasOutput(#GADGET_Canvas))
Box(0, 0, 380, 380, $FFFFFF)
StopDrawing()
EndIf
;}
Case #GADGET_Load ;{
File$ = OpenFileRequester("Load Image...", "", "JPEG Images|*.jpeg|All Files|*.*", 0)
If File$
If LoadImage(#IMAGE_LoadSave, File$)
If StartDrawing(CanvasOutput(#GADGET_Canvas))
Box(0, 0, 380, 380, $FFFFFF)
DrawImage(ImageID(#IMAGE_LoadSave), 0, 0)
StopDrawing()
EndIf
FreeImage(#IMAGE_LoadSave)
Else
MessageRequester("CanvasGadget", "Cannot load image: " + File$)
EndIf
EndIf
;}
Case #GADGET_Save ;{
File$ = SaveFileRequester("Save Image...", File$, "JPEG Images|*.jpeg|All Files|*.*", 0)
If File$ And (FileSize(File$) = -1 Or MessageRequester("CanvasGadget", "Overwrite this file? " + File$, #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes)
If CreateImage(#IMAGE_LoadSave, 380, 380, 24) And StartDrawing(ImageOutput(#IMAGE_LoadSave))
DrawImage(GetGadgetAttribute(#GADGET_Canvas, #PB_Canvas_Image), 0, 0)
StopDrawing()
If SaveImage(#IMAGE_LoadSave, File$, #PB_ImagePlugin_JPEG) = 0
MessageRequester("CanvasGadget", "Cannot save image: " + File$)
EndIf
FreeImage(#IMAGE_LoadSave)
EndIf
EndIf
;}
EndSelect
EndIf
Until Event = #PB_Event_CloseWindow
Destroy_Curseurs()
EndIf