'How do I set a custom cursor when the mouse is over a certain control?
I want to change the cursor when the mouse is over a certain control. I'm having the png of my cursor. How do I achieve it in C++ ?
I tried like this as described here
HCURSOR hcur;
hcur = ::LoadCursorFromFile("cursor.png");
::SetSystemCursor(hcur,OCR_NORMAL);
but it says OCR_NORMAL is undefined.
HINSTANCE hInst;
hInst = GetModuleHandle(NULL);
HCURSOR hCurs;
hCurs = LoadCursor(hInst, MAKEINTRESOURCE(2));
::SetSystemCursor(hCurs,OCR_NORMAL);
I tried like that also but its generating weird linker errors like :
Error 2 error LNK2019: unresolved external symbol "extern "C" struct HICON__ * __stdcall LoadCursorW(struct HINSTANCE__ *,wchar_t const *)" (?LoadCursorW@@$$J18YGPAUHICON__@@PAUHINSTANCE__@@PB_W@Z) referenced in function "int __cdecl main(void)" (?main@@$$HYAHXZ) C:\Users\Diozz\Documents\Visual Studio 2013\Projects\Scroller\Scroller\main.obj
I'm placing the png in the project directory, hoping its right.
So, how will I set the cursor?
Solution 1:[1]
If you want to change the cursor when it's over a particular control, you need to handle the WM_SETCURSOR
message for that control's window. Upon receipt of this message, you will call the SetCursor
function to set the cursor that should be displayed. This function takes a single parameter, a handle to the cursor (HCURSOR
). For more background on this, you should definitely read Raymond Chen's article, "What is the process by which the cursor gets set?"
Under no circumstances, then, would you be calling the SetSystemCursor
function. That function gives you a way to change the global cursor settings—you know, the same ones you change in the Mouse control panel. That's up to the user to change, if she wants to customize her desktop. Applications should leave that alone. It's totally fine if you want to display a funky cursor over a control in your application, but it is not okay if you replace the system-wide arrow cursor with a funky one!
With that out of the way, we don't really have to worry about the right way to call SetSystemCursor
. So let's look at loading cursors. You have already found the LoadCursorFromFile
function, and indeed, this one does exactly what its name suggests. You give it a path to a CUR file, and it loads it right up as a cursor, passing you a handle to that cursor (HCURSOR
). But, other than for testing purposes, you probably won't find yourself ever using LoadCursorFromFile
. Why? Because you don't want to have to deploy a CUR file along with your application. If that file ever gets deleted, or isn't included, your application stops working.
Instead, a cursor should be linked directly into your application's binary. Fortunately, Windows provides a way to do this as part of an binary's resources. You've surely seen the resource file if you've done any Windows programming before. To an RC file, you can add a cursor resource, which amounts to specifying the path to the ICO file. The resource compiler then does the rest, embedding that cursor directly into your EXE. With that done, at runtime, you don't have to rely on a fragile path anymore, you just call LoadCursor
to load the cursor from a resource. (All resources are given a numerical ID, defined in a header file called Resource.h. Let's assume yours has the ID IDC_FUNKY
.)
HINSTANCE hInstance = ::GetModuleHandle(NULL); // get a handle to the app's instance
HCURSOR hCursor = ::LoadCursor(hInstance, MAKEINTRESOURCE(IDC_FUNKY));
You have now loaded your funky cursor from a resource embedded into your EXE. Of course, LoadCursor
can also be used to load pre-defined system cursors. To do this, you pass NULL
for the first parameter, because rather than loading it from an application's resource, you're loading it from the system. For example, let's load the help cursor:
HCURSOR hCursorHelp = ::LoadCursor(NULL, IDC_HELP);
Great—now we know how to load a cursor. All except for one thing: all of the custom cursors we've dealt with have been stored as CUR (or ANI) files. You mention in the question that you want to load a cursor from a PNG file. Honestly, my suggestion would be just not to do that. Use a cursor creation program that can convert your PNG file into a CUR file, and simply use the CUR file. Otherwise, you're going to be busy writing a bunch of pointless code to load a PNG file, convert it to a bitmap, and then convert that bitmap into a cursor. You'll hit a brick wall as soon as you start; there is no obvious way to load a PNG image using the Win32 API. You'll have to either use GDI+, the Windows Imaging Component, or a third-party library that can deal with PNG files. Totally outside the scope of this answer. See here and here if you want to go down this rabbit-hole. Otherwise, download something like Greenfish Icon Editor to do the conversion once and get on with your life.
Putting it all together, then, here's what you should do:
Convert your PNG file to an ICO file, and add this ICO file to your application as a resource. You can do this easily from within Visual Studio.
Write code that calls the
LoadCursor
function to load the cursor from a resource, giving you anHCURSOR
. It would be sensible to do this when your application first starts up, in the initialization routine. Cache the returned handle so that you can use it throughout your application's lifetime. If the control lives on a dialog, you could do this inWM_INITDIALOG
.Handle the
WM_SETCURSOR
message for your control. Although you can do this by subclassing, in most cases, it is easiest just to put the code in the parent's window procedure:static HCURSOR hCursorFunky; ... case WM_SETCURSOR: { // If we're the control that should get the cursor treatment... if (static_cast<HWND>(wParam) == hwndYourControl) { ::SetCursor(hCursorFunky); return TRUE; // indicate we processed this message } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); // do default handling }
Or, if your control lives in a dialog, a slight variation:
case WM_SETCURSOR:
{
if (static_cast<HWND>(wParam) == ::GetDlgItem(hWnd, IDC_YOURCONTROL))
{
::SetCursor(hCursorFunky);
::SetWindowLongPtr(hWnd, DWLP_MSGRESULT, TRUE);
return TRUE; // indicate we processed this message
}
return FALSE; // do default handling
}
One final note: you show a linker error in your question, which suggests that you have not properly told the linker where to find the Windows SDK. All of this business gets set up for you automatically by the "Win32 Application" template in Visual Studio. You should use that to create new projects. If you have not done so, you will need to go into your project's settings and tell the linker to use (at a minimum) kernel32.lib
, user32.lib
, and gdi32.lib
. Otherwise, the linker won't be able to find the Windows API functions that you are trying to call.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Joey Foo |