Link to home
Start Free TrialLog in
Avatar of cdesigner
cdesignerFlag for Russian Federation

asked on

on mouse over static control

I need to get 'OnMouseOver' ( like JavaScript) message, when cursor over static control
ASKER CERTIFIED SOLUTION
Avatar of mikeblas
mikeblas

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 naveenkohli
naveenkohli

static control is like other controls that handle WM_MOUSEHOVER message. You can add this message handle in your parent window class (e.g. dialog box) and when WM_MOUSEHOVER message comes, then you can check what child window this message belongs to.

Or the second way will be to derive a class from CStatic and then handle the mouse over message there and do your special stuff for this static control.
For details you can look into Microsoft System's Journal 's archives. E.g. you can down download code for April 1998 and there you will wind implementation of CStaticLink class.
> WM_MOUSEHOVER message.

This can work, too, but it isn't available on Win95.

..B ekiM
MIKE: Actually it is .. if you use _TrackMouseEvent (note the leading '_')

This will call the native WinNT TrackMouseEvent on NT or emulate it (ie do the same thing) on Win95/98.

You get that as long as you have IE4.x or later (which you probably want to ensure by shipping the appropriate free comctl update exe from ms)

If you want something to happen when the mouse moves away from the control, then just looking at mouse moves is not enough. Best is to use _TrackMouseEvent.  Sedon best is to set up a timer in your OnMouseMove that, when it fires, gets the current mouse position and checks if you are still over the control.

> Actually it is .. if you use _TrackMouseEvent (note the leading '_')

I don't see docs for that function.  Using an undocumented function is a bad idea, I think.


 > This will call the native WinNT TrackMouseEvent on NT or
 > emulate it (ie do the same thing) on Win95/98.

Er, who emulates it?

..B ekiM
Using an undocumented API is indeed bad .. but this is not undocumented.  It is documented by MS in the Platform SDK .. did you actually try looking it up - you just type in _TrackMouseEvent in the search box :-) ?

In case you cannot find it, here is the documatation from the Patform SDK...
>>> 
Platform SDK: Windows User Interface

_TrackMouseEvent

The _TrackMouseEvent function posts messages when the mouse pointer leaves a window or hovers over a window for a specified amount of time. This function calls TrackMouseEvent if it exists, otherwise it emulates it.

Requirements
  Windows NT/2000: Requires Windows 2000 (or Windows NT 4.0 with Internet Explorer 3.0 or later).
  Windows 95/98: Requires Windows 98 (or Windows 95 with Internet Explorer 3.0 or later).
  Header: Declared in Commctrl.h.
  Library: Use Comctl32.lib.

See Also
Mouse Input Overview, Mouse Input Functions, SystemParametersInfo, TrackMouseEvent, TRACKMOUSEEVENT

Built on Wednesday, December 01, 1999
>>>

Also not the docs for TrackMouseEvent...
>>>
Note  The _TrackMouseEvent function calls TrackMouseEvent if it exists, otherwise _TrackMouseEvent emulates TrackMouseEvent. The _TrackMouseEvent function is in commctrl.h and is exported by COMCTRL32.DLL.
>>>

Do you feel warm and fussy about using it now?? :-)
Basically, if you want mouse tracking, then use _TrackMouseEvent in preference to TrackMouseEvent as the former works on more platforms.

Failing that you'll need to perform similar functions yourself using timers, mouse captures and mouse move messages (yuck).  Or you can do some fiddling around in the message pump (I was doing that before TrackMouseEvent came along).
Also see MSJ article Dec 1998, C++ Q&A by Paul DiLascia (see the easrlier Oct 1998 for how to do it without TrackMouseEvent).

Here is the relevant part of it .. interesting reading:
>>>
Flyby

OK, on to the Flyby program from the October 1998 issue. This program illustrates how to implement a mouseover feature by drawing an ellipse that turns red when you move the mouse over it. The basic idea is to use WM_
MOUSEMOVE to know when the mouse has moved in or out of the ellipse. The only problem is that if the user moves the mouse quickly outside your window, you won't get a WM_
MOUSEMOVE. To implement mouseover, you need to know when the mouse has left your window entirely.

I told you to use TrackMouseEvent if you're only writing for Windows NT®, but if you need to run in Windows® 9x as well, I showed you how to set a timer to check whether the mouse has moved outside the window. Why this hack? Because TrackMouseEvent isn't available in Windows 9x, and SetCapture and ReleaseCapture don't work either because, as the official documentation states: "Mouse capture is also affected by the Windows 95 and Windows NT localized input queues.... If the mouse is captured while the mouse button is up, the window receives mouse input only as long as the mouse is over that window or another window created by the same thread."

Well, contrary to what the documentation states, even if the user moves the mouse completely outside your window, your program will still get WM_MOUSEMOVE messages if you've captured the mouse. This is true in both Windows 9x and Windows NT. Several readers sent email pointing this out and suggested that SetCapture is better than my set-a-timer hack. While the premise of this reasoning is correct-SetCapture does in fact capture out-of-window movement (kind of like an out-of-body experience)-the conclusion that SetCapture is a better way to implement mouseover doesn't follow. SetCapture is still unacceptable for another reason: once you capture the mouse, your app no longer receives keyboard input. For example, if your program has captured the mouse and the user types Alt+F hoping to get the File menu or Alt+F4, nothing happens. Oh well.

SetCapture is mostly intended for implementing the down/up action for custom-draw buttons. The user presses the mouse button, your app gets WM_LBUTTONDOWN, you draw your custom button in the down state, and then you capture the mouse until
the user releases the mouse button. At which point you redraw your button in the up state. SetCapture isn't very useful as a general means of finding out when the mouse has left your window. So it seems the set-a-timer thing is the best you can do.

But wait! The friendly Redmondtonians recently informed me that while TrackMouseEvent is not supported for Windows 9x, there's a _TrackMouseEvent in comctl32.dll. This function calls the "real" TrackMouseEvent if there is one (Windows NT); otherwise it duplicates the functionality itself (Windows 9x). Evidently Microsoft Internet Explorer 4.0 software needs TrackMouseEvent, no doubt to implement the very mouseover feature I'm discussing.

As soon as I found out about _TrackMouseEvent, I rewrote Flyby to use it. At first, the implementation seemed straightforward. When the user moves the mouse into your view, the first thing you do is call _TrackMouseEvent.

void CMyView::OnMouseMove(UINT nFlags, CPoint pt)
{
    if (!m_bTrackLeave) {
        // First time mouse entered my window:
        // request leave notification
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.hwndTrack = m_hWnd;
        tme.dwFlags = TME_LEAVE;
        _TrackMouseEvent(&tme);
        m_bTrackLeave = TRUE;
    }
·
·
·
}

When the user moves the mouse outside my view, Windows sends it a WM_MOUSELEAVE message. When the view gets the message, it turns off the highlight and repaints the window-simple.

LPARAM CMyView::OnMouseLeave(
    WPARAM wp, LPARAM lp)
{
    m_bTrackLeave = FALSE;
    if (m_bHilite) {
        m_bHilite = FALSE;
        Invalidate(FALSE);
        UpdateWindow();
    }
    return 0;
}
Like I said, it seems straightforward. But there's a major fly in the ointment of Flyby-or a fly in _TrackMouseEvent, depending on your perspective. If you press Alt+F to invoke the File menu while the mouse is in the middle of the ellipse, Windows sends your view a WM_TRACKLEAVE message, even though the mouse hasn't left your window! (A typical Windows conundrum: when is leaving a window not leaving it?) In this situation, the previous code will unhighlight the ellipse, which is probably not what you want-I mean, the mouse is still inside it, right? Even in Internet Explorer 4.0, if you move the mouse over a hyperlink to highlight it, then press Alt+F to get a menu, the link remains highlighted.

The way to fix things is to modify OnMouseLeave so it only unhighlights the ellipse if the mouse is actually outside it.

CPoint pt = Mouse;
ScreenToClient(&pt);
BOOL bHilite = GetHighlight(pt);
if (!bHilite) { // if mouse really outside
    Invalidate(FALSE);
    UpdateWindow();
}
CMyView::GetHighlight returns TRUE if the point lies inside the ellipse-but only if the view has focus.

BOOL CMyView::GetHighlight(CPoint pt)
{
    return m_hotRegion.PtInRegion(pt) &&
        CWnd::GetFocus()==this;
}

You want to make sure that the view has focus because you probably don't want to highlight the ellipse/item if your app doesn't have focus. (If you do, you can remove the GetFocus check.) Adding the GetHighlight check fixes the menu bug: if you now press Alt+F while the mouse is inside the ellipse, it remains highlighted.

But now there's another problem. If you cancel the menu by clicking the mouse somewhere outside the view, you don't get another WM_MOUSELEAVE message, so the ellipse remains highlighted. Sigh. Is there any way to make this code work?

Don't despair, there's just one more bit of voodoo to nail it down. When the user cancels a popup menu, Windows sends a WM_EXITMENULOOP message. When that happens, you can once again check the mouse position and highlight or unhighlight your hot spot. There is, however, one little problem: Windows sends WM_EXITMENULOOP to the top-level parent window, not your view. If you want a quick solution, modify your CMainFrame class to handle WM_EXITMENULOOP by passing the message along to the active view.

CMainFrame::OnExitMenuLoop(...)
{
    GetActiveView()->
        SendMessage(WM_EXITMENULOOP, ...);
}

However, I chose to implement mouseover as a feature in the view, not the frame. Why should the frame know anything about it? In order to completely encapsulate the mouseover implementation inside the view, what's needed is a way to trap the parent frame's WM_EXITMENULOOP message. This is exactly what my CSubclassWnd class does (from the June 1997 column). CSubclassWnd lets you catch messages sent to another window. It works by installing its own message proc ahead of the MFC one, and then routes messages to a virtual WindowProc function. CSubclassWnd maintains a map to associate each hooked HWND with its CSubclassWnd object, just like MFC's window handle map. CMyView in Flyby uses CSubclassWnd to catch the frame's WM_EXITMENULOOP message. Figure 6 shows the final implementation. Whew.

view.h

////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal.
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
//
class CMyView : public CView {
private:
    CRect m_rcClient;         // client area rectangle
    CRgn  m_hotRegion;        // elliptic region
    BOOL  m_bHilite;          // highlight the ellipse?
    BOOL  m_bTrackLeave;      // I am tracking mouse leave

public:
    virtual ~CMyView();
    CMyDoc* GetDocument() { return (CMyDoc*)m_pDocument; }

    virtual void OnDraw(CDC* pDC);
    BOOL GetHighlight(CPoint pt);

protected:
    DECLARE_DYNCREATE(CMyView)
    CMyView();
    afx_msg void    OnMouseMove(UINT nFlags, CPoint pt);
    afx_msg LPARAM  OnMouseLeave(WPARAM wp, LPARAM lp);
    afx_msg void    OnSize(UINT nType, int cx, int cy);
    DECLARE_MESSAGE_MAP()
};

view.cpp

////////////////////////////////////////////////////////////////
// FLYBY 1998 Microsoft Systems Journal.
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
#include "StdAfx.h"
#include "Flyby.h"
#include "Doc.h"
#include "View.h"
#include "Mouse.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

IMPLEMENT_DYNCREATE(CMyView, CView)

BEGIN_MESSAGE_MAP(CMyView, CView)
    ON_WM_MOUSEMOVE()
    ON_WM_SIZE()
    ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave)
END_MESSAGE_MAP()

CMyView::CMyView()
{
    m_bHilite = -1;            // invalid state
    m_bTrackLeave = FALSE;    // not traking mouse
}

CMyView::~CMyView()
{
}

//////////////////
// Draw ellipse within client area.
// If mouse is outside the ellipse, draw gray; else red
//
void CMyView::OnDraw(CDC* pDC)
{
    // create brush w/appropriate color: red/gray.
    if (m_bHilite==-1) {
        CPoint pt = Mouse;        // mouse pos in screen coords
        ScreenToClient(&pt);      // convert to client
        m_bHilite = GetHighlight(pt);
    }
    CBrush brush(m_bHilite ? RGB(255,0,0) : RGB(192,192,192));

    // select brush and draw ellipse
    CGdiObject *pOldBrush = pDC->SelectObject(&brush);
    pDC->Ellipse(&m_rcClient);
    pDC->SelectObject(pOldBrush);
}

//////////////////
// Window size changed: update client rectangle and elliptic region.
//
void CMyView::OnSize(UINT nType, int cx, int cy)
{
    m_rcClient.SetRect(0,0,cx,cy);
    m_hotRegion.DeleteObject();
    m_hotRegion.CreateEllipticRgnIndirect(&m_rcClient);
}

//////////////////
// Determine if I should highlight the ellipse based on mouse coords.
// That is, if point is inside ellipse and I have the focus.
//
BOOL CMyView::GetHighlight(CPoint pt)
{
    return m_hotRegion.PtInRegion(pt) &&
        CWnd::GetFocus()==this;
}

//////////////////
// Handle mouse move message: highlight or unhighlight ellipse.
//
void CMyView::OnMouseMove(UINT nFlags, CPoint pt)
{
    if (!m_bTrackLeave) {
        // First time since mouse entered my window: request leave notification
        TRACKMOUSEEVENT tme;
        tme.cbSize = sizeof(tme);
        tme.hwndTrack = m_hWnd;
        tme.dwFlags = TME_LEAVE;
        _TrackMouseEvent(&tme);
        m_bTrackLeave = TRUE;
    }

    BOOL bHilite = GetHighlight(pt);
    if (bHilite != m_bHilite) {
        // mouse has either left or entered the ellipse: repaint
        m_bHilite = bHilite;
        Invalidate(FALSE);
        UpdateWindow();
    }
}

//////////////////
// Mouse left the window.
//
LPARAM CMyView::OnMouseLeave(WPARAM wp, LPARAM lp)
{
    m_bTrackLeave = FALSE;            // no longer tracking
    if (m_bHilite) {
        m_bHilite = FALSE;            // don't highlight
        Invalidate(FALSE);            // force repaint...
        UpdateWindow();
    }
    return 0;
}

Figure 6  CMyView

By this point, you probably wish you'd stuck with my original set-a-timer hack, but setting a timer to periodically check whether the mouse has gone bye-bye is really pretty grody. _TrackMouseEvent is much cleaner. And, just in case you haven't been reading MSJ religiously every month (shame on you), here's the URL where you can find out how to redistribute comctl32.dll with your app: http://msdn.microsoft.com/developer/downloads/files/40comupd.htm.

Incidentally, _TrackMouseLeave also lets you request a message when the mouse has hovered over your window for a specified length of time. Just set TRACKMOUSELEAVE::
dwFlags to TME_HOVER. You can use TME_HOVER to implement tooltip-like features where something happens when the mouse has lingered over your window for a specified period of time. And if you don't like that little underbar in front of the name, you can always put the following lines somewhere in your app:

#ifndef TrackMouseLeave
#define TrackMouseLeave _TrackMouseLeave
#endif

I kind of like the underbar to remind me it's not really part of Windows, at least not in Windows 9x.