Link to home
Start Free TrialLog in
Avatar of cpirrita
cpirrita

asked on

Custom cursors over Window controls

I want to have a user selectable cursor displayed in my
windows, including over Window controls, menu bars, and
in dialog boxes ...
When I create a dialog box I call SetClassWord, passing
GCW_HCURSOR as a parameter, while processing the WM_INITDIALOG msg.  The cursor reverts back to the arrow
when over the menu bar.

For window controls I create a window (invisible with no
width/height) for each predefined type - ie LISTBOX,
COMBOBOX, STATIC ...) and call SetClassWord. This is
done whenever a custom cursor is selected and at when
the main program terminates (to revert to the arrow cursor)

There are stil some spots when the arrow cursor shows up -
inside drop down lists and over some window controls in
common dialog boxes.  I'm wondering what other methods
can be employed to get better results.
ASKER CERTIFIED SOLUTION
Avatar of nietod
nietod

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of nietod
nietod

The way that WM_SETCURSOR is used is that it is sent to the frontmost window under the mouse.  The default window procedure's handler for this message, passeses it to the parent window and this continues until there is no parent window (or until somone overides the handler rather than passing it to the default window procedure), then the procedure returns false.  The child window's handler looks at the code returned value.  It it is true, it just returns true.  If it is false, it looks to see if the mouse is actually in the window, or if it is in one of its child windows.  if it is in a child window, it returns false.  If it is in the window, it sets the cursor to the cursor specfied in the class (or to the default arrow cursor if the mouse is in the non-client area or if there is no cursor for the class).

Thus in this way, when a window gets the WM_SETCURSOR message, it gives its parent window (and its grandparent and so on) an opportunity to set the cursor themselves.  If they do so, they return TRUE (which is passed down the window chain) and the message is effectively ignored by the window.  If none of them handle the message, they will (eventually) return FALSE.  The window procedure will then change the cursor to its default.

Thus you have two ways (at least) to handle this, depending on what you need to get done.  The easiest way may be to handle this message in the parent window.  This allows you to write a WM_SETCURSOR handler for just the parent window and it will effect the behaviour in the child windows.  However, for complex cases it may be easier to handle this by writting an WM_SETCORSOR handler for each child window as well.  

Let me know if you have questions.
Avatar of cpirrita

ASKER

Gave it a brief try last night - I had my main window call SetCursor from the WM_SETCURSOR msg.  Some of my windows are created via DialogBox or
DialogBoxParam. I don't believe they are considered child windows; the cursor
didn't change inside those windows. That cleared up when I put a WM_SETCURSOR
handler in them.  I did experience some cursor toggling when over some pre-defined  window controls; I got around that by initially using a SetClassWord to change
the cursor to NULL for each of the pre-defined window types.

While my custom cursor now does display over the menu bars - It still reverts
back to the arrow when inside a drop-down list or on the button that displays the
dropdown list.  It also toggles when on the scroll bar control.  The controls that gives
me problems were created via the resource editor and are attributes of the control
(eg COMBOBOX with CBS_DROPDOWNLIST;  EDITTEXT with ES_AUTOHSCROLL)
so I'm not sure if something like a window subclass procedure needs to be
defined for those controls...


>> Some of my windows are created via DialogBox or DialogBoxParam.
>> I don't believe they are considered child windows
That is correct, they are not.  A child window is a window that is confined to another window's client area, like a control window or a MDI child window.  Dialogs are "owned" windows.  They can move outside of the owner's client area

>> I'm not sure if something like a window subclass procedure
>> needs to be defined for those controls
Unfortunately, it sounds like it.  If you put a handler in those control's parent window, and it made no difference, then those controls must not be passing the message to the parent window.  (What might be happening is that in some cursor locations, they handle the message directly, and in some locations they pass it on to the default window procedure where it is then passed to the parent.  This sort of logic is used in other places, like setting control colors, for example).  If that is the case, and it sounds like it is, you will have to sub-class the control and handle the message within the control.  (The easy handling would be to always call the defualt window procedure for the message, which will always call the parent window....)

One exception to this might be the scroll bars.  Do you mean "window scroll bars" that appear at the edges of the window (part of the frame) or do you mean "scroll bar controls" that is, seperate windows that appear within the client area of the window?
I meant scroll bar controls.  I'll try sub-classing the controls and see how that works
out.
Note that all the sub-classed controls can use the same window procedure (unless there are other things to be done) even though they are for different controls.
thanks for the info - FYI subclasing did not change the cursor for those controls.  The
breakpoints I had inthe subclass procedure hit but the cursor either stayed an arrow or
it flickered between the arrow and the custom cursor.  On the whole though things look
better.
>>FYI subclasing did not change the cursor for those controls
It should have.  Perhaps the default window procedure was still getting called.  Can you post the window procedure code for the sub-classed controls?
here's a control that displays the custom cursor while in the text box
portion of the control but flickers between ther custom cursor and the
arrow cursor when in the drop down list box.
It's defined in the resource file as :

COMBOBOX     IDC_AD_PLIST,17,30,110,113, CBS_SIMPLE | WS_VSCROLL |
             WS_TABSTOP

 code for the parent window:

  switch (wMsg) {    
    case WM_INITDIALOG:
       hCursor   = GetCursor();    // static var
       hControl  = GetDlgItem(hDlg, IDC_AD_PLIST);  // static var
       SetClassWord(hDlg,     GCW_HCURSOR, NULL);
       SetClassWord(hControl, GCW_HCURSOR, NULL);
       lpfnMyListBox = MakeProcInstance((FARPROC)pListBox, hInstance);
       lpfnListBox   = (FARPROC)GetWindowLong(hControl, GWL_WNDPROC);
       SetWindowLong(hControl, GWL_WNDPROC, (LONG)lpfnMyListBox);
       return TRUE;
     case WM_SETCURSOR:
       SetCursor(hCursor);
       return TRUE;

 subclass procedure for combobox

LONG FAR PASCAL pListBox(HWND hWnd, WORD wMsg, WORD wParam,LONG lParam)
{

  switch (wMsg) {
     case WM_SETCURSOR:
       SetCursor(GetCursor());
       break;
  }
  return CallWindowProc(lpfnListBox, hWnd,wMsg,wParam,lParam);
}


Breakpoints set in the WM_SETCURSOR hit once the cursor is placed within
the drop down listbox.  I tried adding a WM_CREATE portion that set the
cursor for hWnd to NULL but the breakpoint never hit.
In the Subclass procedure the SetCursor(GetCursor()) is a problem.  It effectively does nothing so it is hard to say what that will produce.

It should either do a SetCursor(XXXX) where XXXX is a cursor handle to the cursor you want to use.  (probably stored in a global varaible) OR it should forward the message to the parent window like
return SendMessage(GetParent(),wmsg,wParam,lParam);

In the parent (dialog) window procedure I suspect that the WM_SETCURSOR is not correct.  The hCursor  value it is using may not be initialized.  (I can't tell for sure, but if it is initialized, then this is not a problem).

Actually, the WM_INITDIALOG seems like it might try to initialize the hCursor, but if so, it does it poorly.  again it uses the GetCursor() procedure and there is no telling what that will return.  if you want to use the dialog's default cursor handle, use GetClassWord with the dialog window to get the default cursor for the dialog.

Note that for this to work hCursor must be a global or static variable.  If it is local varaible then the value set on WM_INITDIALOG won't be changed on the WM_SETCURSOR.
I should have explained things a little more than I did.
The main window of my program changes the cursor.  The handle to the cursor is
stored in a static variable.  it is initialized to the arrow cursor
The GetCursor function is a function of mine that returns this static variable.
By the time the code that I listed runs, I have already selected a custom cursor so
the GetCursor function returns a valid handle.  I have already verified via breakpoints
that the listed procedure and the subclass procedure are using a valid cursor handle.

It just dawned on me that from reading your comments it seems like we're talking
about different GetCursor functions - I just checked windows.h and saw another
GetCursor (oops).  I changed my function's name and re-ran it. However I
got the same results (breakpoints still show valid cursor handle)
I can't image how it compiled with the two GetCursor()s running around.

Anyways I missed the obvious problem.  Even though your sub-class window procedure sets the cursor  (correctly it seems) it still calls the original window procedure, which ends up settign the cursor as well.  Don't call the original window procedure for this message.

The easy way to do this is to put the call to the original window procedure in a "default" section of the switch() statement.
I did that and verified I return 0 instead of calling CallWindowProc when the
WM_SETCURSOR msg is processed but didn't get any different results.