Activate Macro When Cursor Goes Over a Shape

MikeG

Well-known Member
Joined
Jul 4, 2004
Messages
845
Office Version
  1. 365
  2. 2019
Platform
  1. Windows
I have a shape called Oval1.

I would like to set things up so that if a user points the cursor over the shape, then a macro will be activated.

I know how to set things up to activate if the user clicks on the shape:

Sub Oval1_Click()
Do my stuff
End Sub

However, in this new case, I do not want them to have to click - just pass over the shape.

Could someone give me the basic macro-form? Is it a macro associated with the worksheet, as opposed to the shape itself?

Thanks,

MikeG
 

Excel Facts

When they said...
When they said you are going to "Excel at life", they meant you "will be doing Excel your whole life".
This is a slimmed down version that just throws a handful of events. I would avoid the mouse move events on ranges in general but they are useful in specific cases. This component is handy for creating interactive applications in Excel. You will have to account for application errors as my server leaves that up to the client. Download the example workbook that provides several examples of utilizing the various events. If you can use this, I have another component that is not as developer friendly (I am the only one who uses it) that provides much more information about ranges, various client positions, defined hotspots that cross ranges, interior hotspots within objects, ect. I have deved a handful of custom interactive applications for schools who love using Excel because of the control they have over the format. That is where these components seem to have some value.

The code is very basic. First, set a reference by browsing for the dll file. Excel should register it for you.

<b>Place this in the general declaration section of a class module such as your worksheet:</b>
Private WithEvents MEvents As WorksheetMouseEvents.MouseEvents

<b>Starting:</b>
Set MEvents = New MouseEvents
MEvents.Setup Application

<b>Stopping:</b>
Set MEvents = Nothing

<b>Events:</b>
Private Sub MEvents_RangeMouseEnter(Target As Range)
Private Sub MEvents_RangeMouseExit(Target As Range)
Private Sub MEvents_RangeMouseMove(Target As Range)
Private Sub MEvents_ShapeMouseEnter(Target As Shape)
Private Sub MEvents_ShapeMouseExit(Target As Shape)
Private Sub MEvents_ShapeMouseMove(Target As Range)
Private Sub MEvents_SheetMouseEnter()
Private Sub MEvents_SheetMouseExit()

The download includes an example workbook and the dll file. The reference will not be set because it will fail anyway. From the VBAIDE, Tool, References, Browse, select the dll.

<A HREF="http://cid-ea73b3a00e16f94f.skydrive.live.com/self.aspx/Mr%20Excel%20Example/WS%20Mouse%20Events.zip" TARGET="_blank">WS Mouse Events.xls.zip</A>
 
Upvote 0
Nice work Tom.

Two other alternatives that do not require an external dll:

1- Using a Timer via a seperate invisible application instance(and without the need to use the VBE object model) .

2- Subclassing the worksheet.

Regards.
 
Upvote 0
Hi Jaafar.

<i>"1- Using a Timer via a seperate invisible application instance(and without the need to use the VBE object model)."</i>

Probably too slow because of cross-process marshalling and the code cannot be precompiled into native code. I have not tried it though and am curious about the peformance. Have you tried it?

<i>"2- Subclassing the worksheet."</i>

I would love to see this! Subclassing from which language? I have tried it in VB6, VB.Net, and C#. I have not been able to get it to work satisfactory in any of these. It does not work in VBA as it's just too slow. Are you doing this from C? Are you speaking of subclassing on the order of processing ALL window's messages or are you speaking of creating hooks? I am speaking of intercepting all messages... Have you managed to subclass the application or only EXCEL7? I am curious about this as well.
 
Upvote 0
Hi Jaafar.

"1- Using a Timer via a seperate invisible application instance(and without the need to use the VBE object model)."

Probably too slow because of cross-process marshalling and the code cannot be precompiled into native code. I have not tried it though and am curious about the peformance. Have you tried it?

"2- Subclassing the worksheet."

I would love to see this! Subclassing from which language? I have tried it in VB6, VB.Net, and C#. I have not been able to get it to work satisfactory in any of these. It does not work in VBA as it's just too slow. Are you doing this from C? Are you speaking of subclassing on the order of processing ALL window's messages or are you speaking of creating hooks? I am speaking of intercepting all messages... Have you managed to subclass the application or only EXCEL7? I am curious about this as well.

Hi Tom.

1- I tried using a timer from a second invisible excel instance and worked much better than running the timer from within the current instance.

2- As for the subclassing approach, i was talking about propper subclassing ie: intercepting all the messages and not about installing a system hook although a Mouse hook can also fit the current scenario very well.

As you rightly said, making subclassing work in VBA is difficult and you are lucky if the application doesn't crash. This is what all hardcore VB programmers have always said but i wouldn't take no for an answer and kept trying and trying until i think i found the "secret" for making it work.
Basically, you need to get inside the Window msg pump via the GetMessage API and ensure that every msg is posted using the PostMessage API while the application is subclassed. This is the only way that i have tried which works well and consistently.

Obviously, this needs an additional running loop which should in theory put an extra strain on the system but somehow it doesn't seem to adversly affect the overall performance of the code.

Here is a Subclassing Approach example :

WORKBOOK DEMO.


Add a Class module to your project , give it the name of : CMouseMove and place the following in it :

Code:
Option Explicit
 
Public Event MouseMove _
(ByVal Target As Object, ByVal X As Single, ByVal Y As Single)
 
 
Private Type POINTAPI
    X As Long
    Y As Long
End Type
 
Private Type MSG
    hwnd As Long
    message As Long
    wParam As Long
    lParam As Long
    time As Long
    pt As POINTAPI
End Type
 
Private Declare Function FindWindow Lib "user32.dll" _
Alias "FindWindowA" _
(ByVal lpClassName As String, _
ByVal lpWindowName As String) As Long
 
Private Declare Function FindWindowEx Lib "user32.dll" _
Alias "FindWindowExA" _
(ByVal hWnd1 As Long, _
ByVal hWnd2 As Long, _
ByVal lpsz1 As String, _
ByVal lpsz2 As String) As Long
 
 
Private Declare Function SetWindowLong Lib "user32" _
Alias "SetWindowLongA" _
(ByVal hwnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As Long
 
Private Declare Function CallWindowProc Lib "user32" _
Alias "CallWindowProcA" _
(ByVal lpPrevWndFunc As Long, _
ByVal hwnd As Long, _
ByVal MSG As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
 
Private Declare Function GetCursorPos Lib "user32.dll" _
(ByRef lpPoint As POINTAPI) As Long
 
Private Declare Function GetMessage Lib "user32.dll" _
Alias "GetMessageA" _
(ByRef lpMsg As MSG, _
ByVal hwnd As Long, _
ByVal wMsgFilterMin As Long, _
ByVal wMsgFilterMax As Long) As Long
 
Private Declare Function TranslateMessage Lib "user32.dll" _
(ByRef lpMsg As MSG) As Long
 
Private Declare Function PostMessage Lib "user32.dll" _
Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
 
'========================================
'System Constantes.
Private Const GWL_WNDPROC As Long = -4
Private Const WM_SETCURSOR As Long = &H20
Private Const WM_MOUSEMOVE As Long = &H200
 
'=====================================
 
'Module variables.
Private hXLDesk As Long
Private lPrevWnd As Long
Private bXitLoop As Boolean
'==============================
 
 
Public Sub InstallHook()
 
    If lPrevWnd = 0 Then
        'subclass the xldesk window.
        hXLDesk = FindWindowEx(FindWindow("XLMAIN", Application.Caption) _
        , 0, "XLDESK", vbNullString)
        lPrevWnd = SetWindowLong _
        (hXLDesk, GWL_WNDPROC, AddressOf TransitionalProc)
        ' Msg pump for safe subclassing !!!!
        Call MessageLoop
    End If
 
End Sub
 
Public Sub ClearHook()
 
    'cleanUp.
    bXitLoop = True
    SetWindowLong hXLDesk, GWL_WNDPROC, lPrevWnd
    lPrevWnd = 0
    hXLDesk = 0
 
End Sub
 
Public Function CallBackProc _
(ByVal hwnd As Long, ByVal MSG As Long, _
 ByVal wParam As Long, ByVal lParam As Long) As Long
 
    Dim loword As Long, hiword As Long
    Dim tPt As POINTAPI
 
    On Error Resume Next
 
    'intercept the WM_SETCURSOR msg.
    Select Case MSG
 
        Case WM_SETCURSOR
 
            'check the lparam hiword for mouse moves.
            GetHiLoword lParam, loword, hiword
 
            If hiword = WM_MOUSEMOVE Then
                GetCursorPos tPt
                RaiseEvent MouseMove _
                (Application.ActiveWindow.RangeFromPoint _
                (tPt.X, tPt.Y) _
                , tPt.X, tPt.Y)
            End If
 
    End Select
 
    'process other msgs.
    CallBackProc = CallWindowProc _
    (lPrevWnd, hwnd, MSG, wParam, ByVal lParam)
 
End Function
 
 
Private Sub MessageLoop()
 
    Dim aMsg As MSG
 
    bXitLoop = False
 
    On Error Resume Next
 
   'ensure all Msgs are posted during the subclassing.
    Do While GetMessage(aMsg, 0, 0, 0) And bXitLoop = False
        DoEvents
        PostMessage 0, aMsg.message, aMsg.wParam, aMsg.lParam
    Loop
 
End Sub
 
Private Sub GetHiLoword _
(lParam As Long, ByRef loword As Long, ByRef hiword As Long)
 
   ' this is the LOWORD of the lParam:
    loword = lParam And &HFFFF&
    ' LOWORD now equals 65,535 or &HFFFF
    ' this is the HIWORD of the lParam:
    hiword = lParam \ &H10000 And &HFFFF&
    ' HIWORD now equals 30,583 or &H7777
 
End Sub

Put the following in ThisWorkBook Module :

Code:
Option Explicit
 
Private WithEvents Workbook_ As CMouseMove
 
Private Sub Workbook__MouseMove _
(ByVal Target As Object, ByVal X As Single, ByVal Y As Single)
 
    Static lOldRangeColor As Long
    Static oOldRange As Range
    Static OldX As Single
 
    On Error Resume Next
 
    If Not Target.Parent Is Sheets(1) Then Exit Sub
 
    Select Case True
 
    Case TypeName(Target) = "Range"
 
        If Target.Address <> oOldRange.Address Then
            With oOldRange
                .Interior.ColorIndex = lOldRangeColor
            End With
            Set oOldRange = Target
            With Target
                lOldRangeColor = .Interior.ColorIndex
                .Interior.ColorIndex = 3
            End With
        End If
        With ActiveSheet.Shapes("Oval 1").Fill.ForeColor
            If .SchemeColor = 13 Then .SchemeColor = 7
        End With
        ActiveSheet.Shapes("Oval 1").TextFrame.Characters.Text = ""
 
        Range("a1") = "Current Cell ..." & Target.Address
 
    Case TypeName(Target) = "Nothing"
 
        MsgBox "You exit the worksheet."
 
    Case Target.Name = "Oval 1"
 
        Target.ShapeRange.Fill.ForeColor.SchemeColor = 13
        Target.Characters.Text = vbNewLine & "      hello!"
 
    Case Target.Name = "Oval 2"
 
        With Target.ShapeRange
            If OldX < X Then
                .IncrementRotation -14
                .Fill.ForeColor.SchemeColor = 10
            Else
                .IncrementRotation 14
                .Fill.ForeColor.SchemeColor = 12
            End If
        End With
 
    Case Else
 
        With oOldRange
            .Interior.ColorIndex = lOldRangeColor
        End With
 
    End Select
 
    OldX = X
 
End Sub
 
Public Sub SetHook()
 
    Set Workbook_ = New CMouseMove
 
    Set oMMv = Workbook_
 
End Sub

And finally, put the following in a Standard Module and run the StartMouseEvent Routine :


Code:
Option Explicit
 
Public oMMv As CMouseMove
Private bHookSet As Boolean
 
Public Sub TransitionalProc( _
ByVal hwnd As Long, ByVal MSG As Long, _
ByVal wParam As Long, ByVal lParam As Long)
 
    Call oMMv.CallBackProc(ByVal hwnd, ByVal MSG, _
    ByVal wParam, ByVal lParam)
 
End Sub
 
Sub StartMouseEvent()
 
    If bHookSet = False Then
        bHookSet = True
        Call ThisWorkbook.SetHook
        Call oMMv.InstallHook
    End If
 
End Sub
 
Sub StopMouseEvent()
 
    Call oMMv.ClearHook
    Set oMMv = Nothing
    bHookSet = False
 
End Sub

If I find the time, i'll post an example using a Timer that runs from a second excel instance.

Regards.
 
Upvote 0
Thanks Jaafar. I am looking forward to trying this out. I am for the most part, a hobbyist when it comes to programming so I only choose work that I enjoy. My main job is actually driving a truck. I love playing around with Excel. It's a fantastic application IMO.

The performance gain of placing your code into a component might be worth looking into. Timers are not such a bad thing when used correctly. The windows operating system is full of timers from what I understand. Anyway, I'll play around with this and see if it works as well as I hope.

I suppose that your MessageLoop procedure is to compensate for missed messages when subclassing normally? Would it be possible to use several hooks with filters? I am fairly certain that you can create message filters when subclassing. If so, I wonder if we would be able to intercept all of the messages with two hooks and two filters? This might be less demanding than a continuous loop. Will see when I have some time. I'm up in beautiful Devils Lake, North Dakota making a delivery. Why do people live up here? *smiles*
 
Upvote 0
This works great! I hope you don't mind if I play around with this. Have a good one.
 
Upvote 0

Forum statistics

Threads
1,215,222
Messages
6,123,716
Members
449,116
Latest member
Aaagu

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top