10

INTRODUCTION AND RELEVANT INFORMATION:

I have made an application that needs to change the look of a cursor into hand when mouse hovers above the static control, but resets it to a normal cursor otherwise.

My initial application was in full screen mode, but recently terms have changed and it must have a resizable window.

This means that my handler for WM_SETCURSOR must be rewritten to reflect newly introduced changes.

Cursors are loaded in WM_CREATE, and I have defined class cursor, like this:

   // cursors 
   case WM_CREATE:
      hCursorHand = LoadCursor( NULL, IDC_HAND );
      hCursorArrow = LoadCursor( NULL, IDC_ARROW );
      // other stuff

In my class:

   WNDCLASSEX wc;
   // ...
   wc.hCursor = hCursorArrow;
   //...

This is my old WM_CURSOR handler ( code is simplified for clarity purposes ):

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
             SetCursor(hCursorHand);
        else
             SetCursor(hCursorArrow);
        return TRUE;

If cursor hovers above static control, then my handler changes it to hand, else sets it to default cursor ( arrow ).

Bellow is the picture I have sketched in Paint that displays the desired look of the cursor when it hovers above static control, it is on the client area, and when user resizes window.

enter image description here

If additional code snippets are required, ask and I will edit my post, but for now, they are omitted to keep the post short and concise.

I work on Windows XP, using MS Visual Studio C++ and pure Win32 API.

MY EFFORTS TO SOLVE PROBLEM:

Bellow are the code snippets that I have tried, but they all failed:

First snippet:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        else
             return DefWindowProc( hWnd, msg, lParam, wParam );

Second Snippet:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        break; // based on MSDN example

Third snippet:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE; 
        }
        else
             return FALSE;

These set cursor to hand no matter where it is.

If I leave my WM_SETCURSOR handler unchanged, the only problem I get is that instead of sizing arrows, I get regular arrow ( as the cursor’s look ) when I hover over the border, but window can be sized.

If I comment out my WM_SETCURSOR handler, sizing arrows and cursor arrow appear properly, but cursor doesn’t change into hand when hovers above static control ( which is logical, since there is no WM_SETCURSOR handler to change it ).

I have browsed through SO archive, looked on MSDN, CodeProject , DaniWeb, Cprogramming and CodeGuru, but had no success.

Looking through those, I have found an example where people compare low word of the lParam against hit test code.

Looking through MSDN I have found link for hit test values ( http://msdn.microsoft.com/en-us/library/windows/desktop/ms645618%28v=vs.85%29.aspx ) and I have found link for cursor types (http://msdn.microsoft.com/en-us/library/windows/desktop/ms648391%28v=vs.85%29.aspx ).

Currently I am reading them, because I think that I will have to load additional cursor resources, take several comparisons of hit test values, and then use those resources to set cursor look accordingly.

QUESTION:

I really would like my WM_SETCURSOR handler to look like this:

   case WM_SETCURSOR:
        if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
        {
             SetCursor(hCursorHand); 
             return TRUE;
        }
        else
             // reset cursor's look to default

so I ask the community to instruct me on how to do this.

If this isn’t possible, then I will consider using multiple if statements to check the hit test code, and set cursor’s look accordingly.

Of course, if there is better solution for my problem, please suggest it, I will consider it as well.

Thank you.

Regards.

AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • Does the static control cover the entire client area of the window? – Stuart Oct 08 '13 at 20:38
  • @Stuart,Mr.Stuart static control doesn't cover the entire window area, only a portion of it.Regards. – AlwaysLearningNewStuff Oct 08 '13 at 20:44
  • I see from your comments to the answer that you have solved your problem, but I'm curious about something. I wrote a little test program, where I created a frame window with a static control as a child window, but in my case when WM_SETCURSOR is sent to the frame window the WPARAM is always a handle to the frame window and not the static control. So of course in this case the code you use will never work. (HWND)wparam will never equal to GetDlgItem(hwnd, 4000). So I'm curious, did you create the static class from the "STATIC" window class? – Stuart Oct 09 '13 at 00:27
  • @Stuart, Mr.Stuart here is the code for creating the static control: `HWND SomeStatic = CreateWindowEx( 0, L"Static", L"", WS_VISIBLE | WS_CHILD | SS_NOTIFY | SS_OWNERDRAW, 20, 50, 150, 100, hwnd, (HMENU)4000, hInst, 0);`. Note that I had to owner draw control and that it sends notifications to my main window. Maybe adding style `SS_NOTIFY` will solve your problem. Good luck, and ask again if you need anything. Regards. – AlwaysLearningNewStuff Oct 09 '13 at 00:43
  • @Stuart,Strange... I have made a demo project, and I have problems with the same code I have pasted from here... – AlwaysLearningNewStuff Oct 09 '13 at 00:56
  • @Stuart,Mr.Stuart please try to leave out call to default window procedure like this, and tell me if it works: `case WM_SETCURSOR: if( (HWND)wParam == GetDlgItem( hWnd, 4000 ) ) { SetCursor( cursor ); return TRUE; }` – AlwaysLearningNewStuff Oct 09 '13 at 01:24
  • I don't understand the code you just posted. Is there a break after it, or do you just drop down into the following case statement. In any case, did you create the static control with SS_NOTIFY. If you didn't your code will not work. – Stuart Oct 09 '13 at 01:39
  • @Stuart,I have created it with `SS_NOTIFY`. There is no `break`, I just drop down. It was the only way for me to get it working. I have created default Win32 project in Visual Studio, and got problems with this code. If you have managed to pull this off, can you please tell me how did you do it? Regards. – AlwaysLearningNewStuff Oct 09 '13 at 02:09
  • ok, i'll write an answer exlaining what I think is going on. – Stuart Oct 09 '13 at 02:16
  • @Stuart,I have already accepted Jonathan's answer, if you could post a comment I'd appreciate it, since it doesn't work in my project. I really don't get it, it works in my current project, but when I make new one it just doesn't work. Thank you. Regards. – AlwaysLearningNewStuff Oct 09 '13 at 02:51
  • @Stuart,Mr.Stuart you are right about `SS_NOTIFY`, but I still don't understand why the part where I return `DefWindowProc(...)` bugs... Any thoughts? Regards. – AlwaysLearningNewStuff Oct 09 '13 at 03:07
  • Apparently the people behind this system don't like long discussions in the comments, so I've written a new answer. Take a look and if you have any questions you can post comments there. – Stuart Oct 09 '13 at 03:51

3 Answers3

19

In general, if you handle the WM_SETCURSOR message you must either

  • Call SetCursor() to set the cursor, and return TRUE, or
  • If the message came from a child window, return FALSE for default processing, or
  • If the message is from your own window, pass the message through to DefWindowProc()

I think the last two points aren't made quite clear by the MSDN docs.

The window under the mouse pointer gets the first WM_SETCURSOR message. If it handles it and returns at that point, nothing else happens. If however it calls DefWindowProc(), then DWP forwards the message to the window's parent to handle. If the parent chooses not to handle it, it can return FALSE and the DefWindowProc processing will continue.

But this only applies if the message came from a previous call to DWP. If the message originated with the window itself, rather than a child, returning TRUE or FALSE without setting the cursor means the cursor won't be set at all.

Another thing: although your question didn't specify, I'm assuming from your use of GetDlgItem() that your top-level window is a dialog. If that's true, you can't just return TRUE or FALSE for a message - you need to return the value using SetWindowLongPtr() and store the return value in DWLP_MSGRESULT. Returning FALSE from a dialog procedure indicates that you didn't handle the message at all - this is equivalent to passing a message through to DefWindowProc().

So I think the proper handling for your situation is, in your top-level window:

case WM_SETCURSOR:
    if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
    {
        SetCursor(hCursorHand); 
        SetWindowLongPtr(hwnd, DWLP_MSGRESULT, TRUE);
        return TRUE;
    }
    return FALSE;

If your top-level window isn't in fact a dialog, you would do this:

case WM_SETCURSOR:
    if( (HWND)wParam == GetDlgItem( hwnd, 4000 ) ) 
    {
        SetCursor(hCursorHand); 
        return TRUE;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • Mr.Potter thank you for swift reply. My window is not a dialog, it is made via `CreateWindowEx`. I will try out your solution and report results. Thank you.Regards. – AlwaysLearningNewStuff Oct 08 '13 at 20:40
  • @AlwaysLearningNewStuff: I edited the code for "not a dialog" as I'd inadvertently left the `SetWindowLongPtr()` call in when I pasted it - make sure you don't use that as it will have undefined behaviour for a non-dialog – Jonathan Potter Oct 08 '13 at 20:50
  • Mr.Potter thank you so much! Everything works now. +1 from me. Regards. – AlwaysLearningNewStuff Oct 08 '13 at 21:29
  • Mr.Potter, it seems that my first snippet was correct. The problem seems to be in defining the cursor for a window class. I use cursor handle that hasn't been initialized yet, since the cursor is loaded in `WM_CREATE`. After loading the cursor the standard way ( `wc.hCursor = LoadCursor( NULL, IDC_ARROW );` instead of `wc.hCursor = hCursorArrow;` ) everything worked. Can you verify my conclusion, as better and more experienced developer? Thank you. Regards. – AlwaysLearningNewStuff Oct 08 '13 at 21:42
  • Yes you're correct, you can't use a cursor you load in `WM_CREATE` when you register the window class, since the window at that point hasn't been created! Actually if it's just the stock cursors like `IDC_ARROW` and `IDC_HAND` there's no real benefit in caching their values at all - just call `LoadCursor(NULL, IDC_ARROW);` etc. whenever you need to use it. – Jonathan Potter Oct 08 '13 at 23:01
1

Here's my first example, if cursor go to menubar, cursor changes to cursor hand:

HCURSOR cursorHand = LoadCursor(NULL, IDC_HAND);
case WM_SETCURSOR:
    if(LOWORD(lParam) == HTMENU)
    {
          SetCursor(cursorHand);
    }
 break;

Here's my second example, if cursor goes to button, cursor changes to cursorHand:

HCURSOR cursorHand =  LoadCursor(NULL, IDC_HAND);

case WM_SETCURSOR:
      if(LOWORD(lParam) == buttonId)//declare you
      {
            SetCursor(cursorHand);
       }
   break;

Warning:menubar and button is not created! Create you and please, check my answer example.

GobeRadJem32
  • 102
  • 3
  • 14
0

The question as I understand it is, given a parent window with one more more child windows (one of which is a static control) how do you set the cursor to the hand when the cursor is over the static control, and the arrow when the cursor is over the client area of the parent window, and let default processing take place when the cursor is over a non-client area of the parent window.

To check things out, I wrote a simple program with a top-level window with a static control as a child window. My first attempt was the following:

1) Set the class cursor of the top-level window to LoadCursor(NULL, IDC_ARROW). This allows the default processing to set the cursor to the arrow when appropriate.

2) Keep track of the position of the mouse cursor by handling the WM_MOUSEMOVE message by calling my HandleWMMouseMove function as follows:


case WM_MOUSEMOVE:
    HandleWMMouseMove(lParam);
    break;

//ptCurrMousePos is a global variable of type POINT
static void     HandleWMMouseMove(LPARAM mousepos)
{
    ptCurrMousePos.x = (int)(short)(LOWORD(mousepos));
    ptCurrMousePos.y = (int)(short)(HIWORD(mousepos));
}

3) and then all I had to do was handle the WM_SETCURSOR message by calling my HandleWMSetCursor function as follows:


    case WM_SETCURSOR:
        if (HandleWMSetCursor())
            return TRUE;
        break;

//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL     HandleWMSetCursor(void)
{
    if (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic) {
        SetCursor(hCursorHand);
        return TRUE;
    }

    return FALSE;
}

this worked fine but I couldn't understand how the code posted in the question was working even partially. So I asked the questioner whether the child window was really a static control. The answer was yes but it was created with the SS_NOTIFY style. So I created my child window with this style and my code stopped working, but the code posted with the question started to work. With the help of the Spy++ program distributed with Visual Studio I learned the following.

Static controls are normally transparent (not in the visual sense, but meaning that even when the mouse is over the transparent window, Windows will consider the mouse to be over the window underneath the transparent window).

When you create a static control with SS_NOTIFY the control is no longer transparent (i.e. it starts to receive an process mouse messages (like WM_MOUSEMOVE), so my code stopped working because it never received WM_MOUSE messages when the cursor was over the static control. On the other hand the code in the question did not work when the static control was created without SS_NOTIFY because without this style the static control was transparent, which meant that the WARAM in the WM_SETCURSOR message was never equal to the static control (in other words Windows never considered the mouse to be over the static control because it was transparent).

It is possible to combine the two approaches by changing the WM_SETCURSOR handler function to the following:


//hwndFrame is a handle to the top-level window.
//hwndStatic is a handle to the static control.
static BOOL     HandleWMSetCursor(WPARAM wParam)
{
    if (((HWND)wParam == hwndStatic) || (ChildWindowFromPoint(hwndFrame, ptCurrMousePos)==hwndStatic)) {
        SetCursor(hCursorHand);
        return TRUE;
    }

    return FALSE;
}
Stuart
  • 1,428
  • 11
  • 20
  • In your answer you have said that my code worked, after adding `SS_NOTIFY`, but I myself couldn't produce proper results with Jonathan's answer in a new project that I have created via Visual Studio's wizard. Is there a chance to e-mail me/post on PasteBin/whatever simple project where my code works? In my current project everything works, but it was made as an empty one. However, if I create new project as empty/via wizard, and try it out, it doesn't work properly. Your code might help me to see what did I do wrong. Thank you. Regards. – AlwaysLearningNewStuff Oct 09 '13 at 04:22
  • The full code is too big to post here. Where do you want me to email it to. – Stuart Oct 09 '13 at 04:33
  • Can you see my e-mail in my profile? I am just interested in the Jonathans solution, since it is shorter. Your answer is clear to me, and I believe that it will not bug. Thank you. – AlwaysLearningNewStuff Oct 09 '13 at 04:35
  • If a static control doesn't have the `SS_NOTIFY` style set then `WM_NCHITTEST` returns `HTTRANSPARENT` which I think means the mouse messages including `WM_SETCURSOR` will just fall through to the parent window - they won't appear to come from the child. But if `SS_NOTIFY` is set then `WM_NCHITTEST` returns `HTCLIENT` which means the control gets mouse messages, and so when it calls `DefWindowProc()` for `WM_SETCURSOR` this will get passed to the parent window. – Jonathan Potter Oct 09 '13 at 04:41