3

Description

I am experimenting with mouse-rollover events. On a sheet I have the following layout:

enter image description here

In column A, there are 3 named ranges: RegionOne which is A2:A4 RegionTwo which is A5:A7 and RegionThree which is A8:A10. These Range Names are listed in C1:C3. In D1:D3 I have the following formula:

=IFERROR(HYPERLINK(ChangeValidation(C1)),"RegionOne") (C1 changes to C2, C3 in D2, D3)

Cell F1 is a named range: NameRollover. Cell F2 is a Data Validation cell where Allow: = source that changes according to code execution.

Purpose

When a user rolls the mouse over the range D1:D3 the following happens:

  1. The cell is highlighted according to a Conditional Format
  2. Cell F1 (NameRollover) changes to the highlighted cell content
  3. Cell F2 Data Validation changes the source to the Named Range that matches the value in Cell F1
  4. Cell F2 is populated with the first entry of the data validation list

This is achieved by using the following Private Sub on Sheet1:

Private Sub Worksheet_Change(ByVal Target As Range)
Dim MyList As String
If Not Intersect(Range("F1"), Target) Is Nothing Then
 
 With Sheet1.Range("F2")
    .ClearContents
    .Validation.Delete
    MyList = Sheet1.Range("F1").Value
    .Validation.Add Type:=xlValidateList, Formula1:="=" & MyList
End With

Sheet1.Range("F2").Value = Sheet1.Range(MyList).Cells(1, 1).Value

End If
End Sub

And by using the following Function (in a standard module)

Public Function ChangeValidation(Name As Range)
Range("NameRollover") = Name.Value
End Function

Everything works perfectly, except…

I would like, after the rollover action, for the data validation cell (F2) to become the “active” cell. At the moment, the user has to select that cell unless it already is the active cell. To try and achieve this, I have tried each of the following at the end of the Private Sub before the End If:

Application.Goto Sheet1.Range("F2")
Sheet1.Range("F2").Select
Sheet1.Range("F2").Activate

None of which works.

Question

How can I get the focus to shift at the end of the Private Sub execution to the cell of my choice – in this case F2? All suggestions are welcome.

Siddharth Rout
  • 147,039
  • 17
  • 206
  • 250
  • I am surprised that "everything works perfectly" when `ChangeValidation` function should not work at all. You cannot change a cell value in a udf when called from another cell. Also how are the mouse rollover events triggered? and where's the code that runs when these events are triggered? e.g. What changes cell "F1" to trigger the `Worksheet_Change` callback? – Super Symmetry Dec 12 '20 at 07:27
  • `You cannot change a cell value in a udf when called from another cell.` @SuperSymmetry You [CAN](https://stackoverflow.com/questions/23433096/using-a-udf-in-excel-to-update-the-worksheet) but is not advisable :) – Siddharth Rout Dec 12 '20 at 07:58
  • @SiddharthRout super interesting: thanks for sharing :) – Super Symmetry Dec 12 '20 at 08:11
  • When you call code on the same "thread" (not using that literally but it does seem to be some kind of separate but parallel context) as the function triggered by a hyperlink rollover, it doesn't always behave as you'd expect - some actions do not seem to be available, and making a selection may be one of those. – Tim Williams Dec 12 '20 at 08:52
  • @TimWilliams, thank you for your feedback. Does this mean that it's (probably) not possible to achieve what I'm after - or should I continue the hunt? –  Dec 12 '20 at 11:28
  • Nope my comment was not about any "skepticism". In fact it was a simple question as to how you were conducting the mouse rollover which later Tim explained (*And hence I deleted the comment*). I had that query beacuse that was the only part which was missing in your well drafted question. :) – Siddharth Rout Dec 12 '20 at 12:06
  • @SiddharthRout no worries, I didn't take any offence nor did I mean to offend :) Do you think I'm on a loser here trying to find a solution? –  Dec 12 '20 at 12:09
  • nope. just wanted to clear any misunderstanding :) – Siddharth Rout Dec 12 '20 at 12:28
  • @SiddharthRout Thank you :) Any ideas on this - or do you think Tim Williams has pretty well summed up the likelihood of it being unsolvable? –  Dec 12 '20 at 12:35
  • I concur with what he says but I am trying certain APIs just to be sure before i completely rule this out... – Siddharth Rout Dec 12 '20 at 13:00
  • I managed to find an alternative to HYPERLINK method in case you are interested... If not then feel free to ignore the answer :) – Siddharth Rout Dec 12 '20 at 14:30

1 Answers1

5

Further to Tim's and my comments above, it is not possible to select a cell when you run a procedure through HYPERLINK method. Having said that, I have managed to find an alternative if you are interested. This doesn't use the HYPERLINK method but relies completely on two mouse APIs. GetCursorPos API and SetCursorPos API.

Logic

  1. Find the mouse cursor position.
  2. Find the range directly under the mouse cursor.
  3. Format/Update/Select relevant cells.

Pros:

  1. Doesn't rely on updating/formatting/Selecting a cell from inside an UDF.
  2. Does what you want without the need of helper column (Col D).
  3. You can bypass the F1 cell also if you want and directly take the values from C1:C3. In the below example I am however using F1.

Cons:

  1. One has to Start and Stop the process.
  2. One can see slight screen flickering when the mouse is over range C1:C3.

Test Conditions

For testing purpose, I have created a sample worksheet which looks like this

enter image description here

There are two Form Control Buttons which are bound to StartTracking() and StopTracking() using Assign Macro

Code:

Paste this in a module. We would not need the Worksheet_Change event anymore.

Option Explicit

Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function SetCursorPos Lib "user32" (ByVal x As Long, ByVal y As Long) As Long
  
Type POINTAPI
    Xcoord As Long
    Ycoord As Long
End Type

Dim StopProcess As Boolean
Dim ws As Worksheet

'~~> Start Tracking
Sub StartTracking()
    StopProcess = False
    TrackMouse
End Sub

'~~> Stop Tracking
Sub StopTracking()
    StopProcess = True
End Sub

Sub TrackMouse()
    Set ws = Sheet1
    
    '~~> This is the range which has the names of named range
    Dim trgtRange As Range
    Set trgtRange = ws.Range("C1:C3")
    
    Dim rng As Range
    Dim mouseCord As POINTAPI
    
    Do
        '~~> Get the current cursor location and try to find the
        '~~> range under the cursor
        GetCursorPos mouseCord
        Set rng = Nothing
        Set rng = GetRangeUnderMousePosition(mouseCord.Xcoord, mouseCord.Ycoord)
            
        '~~> Check if the cursor is above C1:C3
        If Not rng Is Nothing Then
            If Not Intersect(trgtRange, rng) Is Nothing Then
                UpdateAndFormat rng
                
                Application.Cursor = xlDefault
            End If
        End If
        
        DoEvents '<~~ Do not uncomment or remove this
        
        If StopProcess = True Then Exit Do
    Loop
End Sub

'~~> Get the range under the cursor
Function GetRangeUnderMousePosition(x As Long, y As Long) As Range
    On Error Resume Next
    Set GetRangeUnderMousePosition = ActiveWindow.RangeFromPoint(x, y)
    On Error GoTo 0
End Function

'~~> Update and format cells F1/F2
Private Sub UpdateAndFormat(rng As Range)
    ws.Range("NameRollover").Value = rng.Value2
    
    With ws.Range("F2")
        .ClearContents
        .Validation.Delete

        .Validation.Add Type:=xlValidateList, Formula1:="=" & _
        ws.Range("NameRollover").Value2
        
        .Value = ws.Range(ws.Range("NameRollover").Value2).Cells(1, 1).Value
        
        Application.ScreenUpdating = False '<~~ To minimize showing the busy cursor
        .Select
        Application.ScreenUpdating = True
        
        '~~> Optional. Feel free to uncomment the below
        '~~> Move the cursor over cell F2. If it stays over C1:C3 then you will
        '~~> get busy cursor icon
        'SetCursorPos _
        ActiveWindow.ActivePane.PointsToScreenPixelsX(.Left + (.Width / 2)), _
        ActiveWindow.ActivePane.PointsToScreenPixelsY(.Top + (.Height / 2))
    End With
End Sub

In Action

enter image description here

Sample File

Mouse over Example

Disclaimer

I have not completely tested this file and may have bugs. Please ensure you have closed all important work before playing with this file.

Siddharth Rout
  • 147,039
  • 17
  • 206
  • 250
  • @ SiddharthRout Thank you! As you say, there are some "cons" with having to start the process, however, it works great as an alternative method - and achieves the same 'end' I was seeking. I have a feeling the clever method you've invented may have applications beyond my specific needs. Thank you again :) –  Dec 12 '20 at 19:44