Page 1 of 2

How to change the mouse cursor

Posted: Thu Nov 06, 2008 8:15 pm
by Mistrel
Here are some examples on how to change the mouse cursor in your application.

The Windows API provides a few functions that allow applications to modify the cursor on the fly. SetCursor is used in conjunction with SetCapture for events where the user clicks and drags the mouse beyond the boundaries of the window. The default cursor for a window is set when RegisterClass is called for the window but it can also be updated by calling SetWindowClass.

For this example if you have the window selected pressing the "Q" key will change the cursor to a walking dinosaur (if you have the cursor \windows\cursors\dinosaur.ani which should come with XP and Vista). The "W" key will change it back to the arrow. This is using the SetWindowClass method. If you hold the left mouse button down inside the window and drag anywhere on your screen you will see an example of the SetCursor method.

Code: Select all

Enumeration
	#Main_Window
EndEnumeration

Procedure WinCallback(hWnd, uMsg, wParam, lParam)
	Shared PrevWndFunc
		Select uMsg
			Case #WM_KEYDOWN
				If wParam=#VK_Q
					WindowsPath.s=Space(#MAX_PATH)
					GetWindowsDirectory_(@WindowsPath.s,#MAX_PATH)
					hCursor=LoadCursorFromFile_(WindowsPath.s+"\Cursors\dinosaur.ani")
					SetClassLong_(WindowID(#Main_Window),#GCL_HCURSOR,hCursor)
				ElseIf wParam=#VK_W
					SetClassLong_(WindowID(#Main_Window),#GCL_HCURSOR,LoadCursor_(0,#IDC_ARROW))
				EndIf
			Case #WM_LBUTTONDOWN
				SetCapture_(hWnd)
				hCursor=LoadCursor_(0,#IDC_HELP)
				SetCursor_(hCursor)
			Case #WM_LBUTTONUP
				ReleaseCapture_()
		EndSelect
	ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

OpenWindow(#Main_Window,0,0,160,120,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
SetWindowCallback(@WinCallback())

Repeat
Until WaitWindowEvent()=#WM_CLOSE

Posted: Thu Nov 13, 2008 7:39 pm
by einander
Mistrel: Nice Code, and useful.

Beware that the folder WindowsPath.s+"\Cursors\" is not present on all installations.
On my spanish XP the folder is WindowsPath.s+"\Cursores\".

Posted: Thu Nov 13, 2008 7:59 pm
by Mistrel
Thanks for the tip. I've done a bit of googling and digging on MSDN but I can't figure out how to get a localized version of this folder. :?

Posted: Thu Nov 13, 2008 8:09 pm
by einander
A ugly brute force way is search a translation for "Cursors" on each language for your target customers.
But this is a general problem that needs a practical solution.

Posted: Thu Nov 13, 2008 8:35 pm
by Mistrel
Or do a generic search for "arrow_i.cur".

Posted: Fri Nov 14, 2008 12:19 am
by Edwin Knoppert
The SetClassLong is a hobby approach.
If you need a cursor use WM_SETCURSOR
I adapted the callback from above to:

Code: Select all

Procedure WinCallback(hWnd, uMsg, wParam, lParam) 
   Shared PrevWndFunc 
      Select uMsg 
         Case #WM_SETCURSOR            
            SetCursor_( hCursor )            
            ProcedureReturn 1
      EndSelect 
   ProcedureReturn #PB_ProcessPureBasicEvents 
EndProcedure 
In this case the cursor was put in a global variable (hCursor)

Posted: Fri Nov 14, 2008 12:24 am
by Mistrel
You cannot use SetCursor without SetCapture. Without SetClassLong you will then be required to track when the mouse has entered/exited the window and set your custom cursor each time. But this will only occur when a callback has taken place so the old cursor may bleed into your application.

Additionally, unless you release the capture your cursor will bleed out of the application as well. Hence why if you don't want your cursor to take over the OS cursor use SetClassLong instead.

Comment out the lines for SetCapture and ReleaseCapture and you'll see that it doesn't work.

Posted: Fri Nov 14, 2008 12:28 am
by Edwin Knoppert
what are you talking about?!
windows makes a request to your window for a new cursor on each WM_SETCURSOR.
It is the suggested place to set a cursor.
Setcapture has nothing to do with it unless you have a specific need.

Using SetClassLong() is contradiction to modular programming.

Posted: Fri Nov 14, 2008 12:31 am
by Edwin Knoppert
I suspect that this topic title is not entrirly what you had in mind.
After reading more i think you needed a specific behaviour.

Posted: Fri Nov 14, 2008 12:33 am
by Mistrel

Code: Select all

Enumeration
   #Main_Window
EndEnumeration

Global hCurcor=LoadCursor_(0,#IDC_HELP)

Procedure WinCallback(hWnd, uMsg, wParam, lParam)
   Shared PrevWndFunc
      Select uMsg
         Case #WM_SETCURSOR
            SetCursor_(hCurcor)
            ProcedureReturn 1
      EndSelect
   ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

OpenWindow(#Main_Window,0,0,160,120,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
SetWindowCallback(@WinCallback())

Repeat
Until WaitWindowEvent()=#WM_CLOSE
Edit: corrected the code so that it does work.

Posted: Fri Nov 14, 2008 12:35 am
by Mistrel
This snippet is how to change the cursor inside of your application. It's not an OS-wide change.

Posted: Fri Nov 14, 2008 12:39 am
by Edwin Knoppert
>I have tested your method
No, you left out an important line.
And you mis the point using this message, reloading the cursor each time is not desired.

>This snippet is how to change the cursor inside of your application. It's not an OS-wide change.
Of course not, that would be hobby stuff.
I am talking about setting the cursor for the window you have put this code in.

But then, you also wish to use SetCapture() to keep the cursor when you leave the window.
That's optional.

Posted: Fri Nov 14, 2008 2:34 am
by Mistrel
Thank you for your suggestion. I posted here because I had just familiarized myself with these methods. Why do you consider the SetClassLong function as a hobby approach? It's simpler because it doesn't require a callback. What is the advantage of using WM_SETCURSOR?

I've also edited the previous snippet for others to reference.

Posted: Fri Nov 14, 2008 7:50 am
by idle
Nice tip Mistrel

Posted: Sat Nov 15, 2008 4:30 am
by yrreti
I also say Nice tip Mistrel.
Thanks for sharing. :)