Microsoft Access 2002 – Emulate hyperlink cursor change

I’m still using Microsoft Access 2002 and thought I’d share this little tip. You know how in web browsers hyperlinks are underlined in blue and your cursor changes to a little hand when you move over them?

Using a slightly bizarre but agreeable undocumented feature you can emulate the cursor change in Microsoft Access 2002.

Now you’d think that you would be able to use the Screen.MousePointer function. However, that doesn’t provide access to the hyperlink hand. Also, brilliantly, if you attempt to replicate this using the built-in Screen.MousePointer function (using “=Screen.MousePointer(11)” in the MouseMove event), you can corrupt your database and crash Microsoft Access! WOW! Now that’s what I call an undocumented feature. You would also think that you could instead place the Screen.MousePointer command in an Event Procedure on the MouseMove event but that simply changes the cursor to a hand until you change it back in code somewhere else.

Example of how to use

This is really simple and works beautifully.

  • Create a module called “Cursor” and paste the code below into it.
  • Create a form and place a label in the middle.
  • Type any caption for the label. Set the label ForeColor to blue and turn FontUnderline on.
  • Set the MouseMove event to “=UseHand()”
  • Save and switch to form view. Move your cursor around the form.

Notice how when you move over the label the cursor changes to a hand and, here’s the undocumented feature aspect, when you move away from the label, it changes back to the normal cursor without any VBA assistance!

In real life

I’ve been using this technique instead of command buttons for buttons that would open a new page or a new form. It really makes your screen feel much less cluttered and provides a user interface that is now much more familiar (web browsers) to users than buttons.

Brilliantly, this also works in continuous forms without any adjustments.

Microsoft Access 2002 - Cursor.UseHand() screenshot

This is a screenshot showing a continuous form on the upper right with a single form on the lower right (that is linked to the continuous)hosted in a single form. There are hyperlink controls on the host form (Products, People, etc), a hyperlink control on the single linked form and on the continuous form. The cursor changes to a hand over all the blue underlined labels and back again everywhere else. Imagine how cluttered this would appear with command buttons. Click the screenshot for the full view.

Cursor Module

Option Compare Database
Option Explicit
' Control the look of the cursor
' Replacement for Screen.MousePointer function
' _____________________________________________________________________________
' CONSTANTS
' Standard cursor IDs
Public Enum SystemCursorID
  IDC_arrow = 32512&
  IDC_IBEAM = 32513&
  IDC_WAIT = 32514&
  IDC_CROSS = 32515&
  IDC_UPARROW = 32516&
  IDC_SIZE = 32640& ' OBSOLETE: use IDC_SIZEALL
  IDC_ICON = 32641& ' OBSOLETE: use IDC_ARROW
  IDC_SIZENWSE = 32642&
  IDC_SIZENESW = 32643&
  IDC_SIZEWE = 32644&
  IDC_SIZENS = 32645&
  IDC_SIZEALL = 32646&
  IDC_NO = 32648& ' not in win3.1
  IDC_HAND = 32649&
  IDC_APPSTARTING = 32650& ' not in win3.1
  IDC_HELP = 32651&
End Enum
' _____________________________________________________________________________
' TYPES
Private Type POINT ' declared here because a point is a rectangle of 1 unit width and eight
  X As Long
  Y As Long
End Type
Private Type RECT
  Left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type
' _____________________________________________________________________________
' DECLARES
Private Declare Function LoadCursor Lib "user32" Alias "LoadCursorA" ( _
                         ByVal hInstance As Long, _
                         ByVal pCursorName As Long) As Long
Private Declare Function ShowCursor Lib "user32" ( _
                         ByVal bShow As Long) As Long
Private Declare Function SetCursorPos Lib "user32" ( _
                         ByVal X As Long, _
                         ByVal Y As Long) As Long
Private Declare Function SetCursor Lib "user32" ( _
                         ByVal hCursor As Long) As Long
Private Declare Function GetCursorPos Lib "user32" ( _
                         lpPoint As POINT) As Long
Private Declare Function ClipCursor Lib "user32" ( _
                         lpRect As Any) As Long
Private Declare Function GetCursor Lib "user32" () As Long
Private Declare Function GetClipCursor Lib "user32" ( _
                         lprc As RECT) As Long

' _____________________________________________________________________________
' GLOBALS
Dim hLastCursor As Long

'''
''' Changes cursors to a hand, normally used to indicate the item below the cursor
''' is a link that can be followed.
'''
'''
''' Example
''' Call in the OnMouseMove Event (instead of calling an [Event Procedure]) of a label
''' to change the cursor to a hand. Doing it this way seems to reset the cursor back to
''' default when you move out of the control's area. Miraculous!
'''
Public Function UseHand()
  Cursor.UseSystemCursor IDC_HAND
  End Function

' Sets the cursor to a system shape
' _____________________________________________________________________________
'
Public Function UseSystemCursor(CursorID As SystemCursorID)

  ' Load new cursor and, if successful, set
  hLastCursor = LoadCursor(0, CLng(CursorID))
  If (hLastCursor > 0) Then
    hLastCursor = SetCursor(hLastCursor)
  End If

  End Function

' Undoes the last cursor change
' _____________________________________________________________________________
'
Public Sub RestoreCursor()

  If hLastCursor > 0 Then
    SetCursor hLastCursor
    hLastCursor = 0
  End If
  End Sub

In later versions of Access and if you are using a control with the Hyperlink Address property, you no longer need this workaround. Simply put a single space in the HyperlinkAddress property. Oddly, it uses a different hand to the system-wide hand cursor. Everywhere you go with Access, you keep running into undocumented features!

31 thoughts on “Microsoft Access 2002 – Emulate hyperlink cursor change

  1. Excellent tip and the code works great. Thank you very much for posting this, makes a few of my forms seems much more interactive.

  2. Great tip. Exactly what i want. But when i move mouse over the field cursor very fast switch from standart to hand and back. It’s like blinking. I suggest to make some modifications to function UseSystemCursor:

    Public Function UseSystemCursor(CursorID As SystemCursorID)
    ‘ Load new cursor and, if successful, set
    hLastCursor = LoadCursor(0, CLng(CursorID))
    If (hLastCursor > 0) Then
    hLastCursor = SetCursor(hLastCursor)
    Else
    RestoreCursor
    End If
    End Function

    I move RestoreCursor calling. No cursor blinking now. Maybe this is bad solution but it works great for me.

  3. Hi Guys,

    thought I would add my thoughts… To get the above effect, I usually:

    Add a label to the form in question
    Enter a space into the Hyperlink address property for the label
    Enter a meaningful word or short phrase into the ControlTip Text property

    Works a treat…. cheers Johny

      1. Been a while and for some reason this has just landed in my inbox…strange :) but you’re very welcome :)

    1. Excellent tip. The original article is an Access 2002 tip, of course, and was needed to add the hyperlink cursor to list boxes and image controls. Though using the Hyperlink Address doesn’t, for some bizarre reason, use the system hyperlink cursor. I wonder what genius at Microsoft thought that up? ‘Shall I use the in-built system hyperlink cursor fine-tuned after years of market use and research or draw my own unchangeable and accessible in no other way cursor?’ Baffling.

  4. Matthew Kaney :

    I am utterly confused, especially considering there are people that claim in worked brilliantly. HOW could this work? The code is totally nonsense

    1) you declare the systemcursorid enum, but nowhere in your code do you use it.
    2) in function UseHand you have an object “cursor” where do you define it? it’s not native to VBA
    3) in this function you use method cursor.usesystemcursor IDC_HAND.. ?? I would assume you meant to use your enum here
    4) in the code you make calls to 2 API functions: SetCursor and LoadCursor, yet you declare GetClipCursor, GetCursor, ClipCursor, GetCursorPos, SetCursorPos, ShowCursor and never use them.

    Have I lost it or are you just full of b.s.?

    I wouldn’t go as far to say as you’ve lost it but I hope this helps…
    1) The enum SystemCursorID is used by UseSystemCursor function as the type for an argument and by UseHand directly to specify IDC_HAND. That is, IDC_HAND is short for SystemCursorID.IDC_Hand.
    2) This code is to be placed in a module called Cursor (as described in the article text). Specifying Cursor.UseSystemCursor speeds execution slightly (it’s the same as using or omitting Me. when accessing Form properties) and lets me know, months down the line, where the function is.
    3) It does use the enum. It is shorthand for SystemCursorID.IDC_HAND. It auto-completes because the UseSystemCursor only takes a SystemCursorID as an argument. Admittedly, I probably should have used the full syntax.
    4) Absolutely right. Two reasons for this. Firstly, when I write a function such as this I tend to gather all the relevant API Declare’s first so that, if I need to at a later date, I can map the remaining API functions to VBA code. Secondly, this article is an extract of my Cursor module and only contains the VBA code that does the cursor-hand thing and the Declare’s were just left in.

    1. My apologies sir, for calling your code nonsense. And thank you for your quick response! I suppose if I would have caught the part about naming the module Cursor, that I would have had more success. Looking at the code again, I also just now realize that SystemCursorID is used in the UseSystemCursor function declaration. For some reason it did not show up when I did a Find for it earlier.

      I was dead tired when I first came across your solution. I was determined to figure out how it worked rather than just cut and paste, but my brain was clearly not functioning. The light in the attic that is my head has finally come on (“OOOoohhhhh!”) Thanks again, this will save another half hour or so for me in my mad rush to get a project working today so I owe you one!

  5. I am utterly confused, especially considering there are people that claim in worked brilliantly. HOW could this work? The code is totally nonsense

    1) you declare the systemcursorid enum, but nowhere in your code do you use it.
    2) in function UseHand you have an object “cursor” where do you define it? it’s not native to VBA
    3) in this function you use method cursor.usesystemcursor IDC_HAND.. ?? I would assume you meant to use your enum here
    4) in the code you make calls to 2 API functions: SetCursor and LoadCursor, yet you declare GetClipCursor, GetCursor, ClipCursor, GetCursorPos, SetCursorPos, ShowCursor and never use them.

    Have I lost it or are you just full of b.s.?

  6. Hi,
    If anyone can clue me in as to what should be the exact text in the On Mouse Move event. Whatever I type is giving syntax and variable not defined errors.
    Thanks
    e.g.

    Private Sub StaffName_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)

    End Function

  7. @Mike: Thanks for the comment. My blog publishing software had replaced symbols with HTML code. The code can now be copied and pasted. Please note that creating a new module sometimes automatically puts a line or two of code in and those lines needs to be removed before pasting in the above code.

  8. Tried the code above in access visual basic 6.5 with MS Access 2002, 2007 and hit the same brick wall.

    it just won’t compile.

    for “Option Explicit” get invalid outside procedure

    for all these statments:
    IDC_arrow = 32512&
    IDC_IBEAM = 32513&   
    IDC_WAIT = 32514&   
    IDC_CROSS = 32515&   
    IDC_UPARROW = 32516&   
    IDC_SIZE = 32640& ‘ OBSOLETE: use IDC_SIZEALL   
    IDC_ICON = 32641& ‘ OBSOLETE: use IDC_ARROW   
    IDC_SIZENWSE = 32642&   
    IDC_SIZENESW = 32643&   
    IDC_SIZEWE = 32644&   
    IDC_SIZENS = 32645&   
    IDC_SIZEALL = 32646&   
    IDC_NO = 32648& ‘ not in win3.1   
    IDC_HAND = 32649&   
    IDC_APPSTARTING = 32650& ‘ not in win3.1   
    IDC_HELP = 32651&   

    get error expected end fo statement.

    for all these statements get compiler error – expected expression:

    Private Declare Function LoadCursor Lib “user32” Alias “LoadCursorA” ( _   
    ByVal hInstance As Long, _   
    ByVal pCursorName As Long) As Long 
     
    Private Declare Function ShowCursor Lib “user32” ( _   
    ByVal bShow As Long) As Long 

    Private Declare Function SetCursorPos Lib “user32” ( _   
    ByVal X As Long, _   
    ByVal Y As Long) As Long  

    Private Declare Function SetCursor Lib “user32” ( _   
    ByVal hCursor As Long) As Long  

    Private Declare Function GetCursorPos Lib “user32” ( _   
    lpPoint As POINT) As Long  

    Private Declare Function ClipCursor Lib “user32” ( _   
    lpRect As Any) As Long  

    Private Declare Function GetCursor Lib “user32” () As Long  

    Private Declare Function GetClipCursor Lib “user32” ( _   
    lprc As RECT) As Long  

    for this statement get compiler error expected :=

    Public Function UseHand()

    for this statement “If (hLastCursor > 0) Then” get expected: ) error

    for this statement “Public Sub RestoreCursor()” get expected := error

    Basically the code is unusable is there something i’m doing wrong.

    I have only just started programming access databases using vb so am very new to this.

    Any help appreciated.

  9. Very nice posting. I am here to share my view about stellar phoenix access repair software which repair and recover corrupted mdb database and MS access files. It is a powerful and robust software and easy to use.

    Thanks

  10. Thanks Thanks Thanks Thanks Thanks Thanks Thanks
    These thanks are coming straight from my heart
    Thank you very much again
    it works wonderful

  11. @Sean

    Feels like a lack of memory issue but I don’t have any firm idea as to what it might be.

    You could try closing the database with a single different form open each time (and once with no forms open) and that will tell you if it is a specific form causing the problem.

    Do you ever create form objects in code (Dim f as New Access.Form Set f = New Access.Form) that you don’t dispose of (Set f = Nothing) when you have finished with them? I’m thinking that perhaps there are a lot of these in memory after you’ve used the application for a while.

    Do you have forms with lots of controls on (I’m talking over seven hundred, this would include a form and its subforms)?

    Is the application linked to a back-end database on a network and it’s trying to compact it on close? Try disabling compact on close (even if not on network) and see if that removes the problem.

    I can’t really be of any more help than that.

  12. We have developed an application in MS Access 2002.
    On occassion when we close the application the whole windows screen flashes with multiple flashes and we have to end process by using CTRL ALT DEL…
    Some pc’s do it some dont. We cant isolate as to what may be causing the problem.

    Would really appreciate your help on this one
    Thanks
    Sean

  13. @Phil Keyes
    Well, there are no superfluous parentheses in the source code printed above so I am wondering where they came from. Curious.

    Anyway, that motivated me to update the post using the new sourcecode facility here at WordPress.

  14. For Access 2003

    That worked great except I had to drop the closing braces () at the end of the declare statements otherwise I would get a “type mismatch” error for the LoadCursor etc.

    so I ended up changing, for example

    Private Declare Function ShowCursor Lib “user32” (ByVal bShow As Long) As Long ()

    to

    Private Declare Function ShowCursor Lib “user32” (ByVal bShow As Long) As Long

    for the firstfour declares … then it worked … but I don’t understand why

  15. Tried this with Vista and Acc 2007 and I get an ugly flashing of the icon on every mousemove… Looks like this technique doesn’t work so well in either Vista or Acc2007 – can anyone else confirm?

    Alternative is to set IsHyperlink=True with HyperlinkAddress =null.

  16. Awwwwwwesomeee! I tried it out and I want to send you a big “Thank You”! I can’t wait to start using this in my database. Those drab Microsoft Buttons are gone for good thanks to you! Just think of the space I will save!

Leave a reply to Mister Slimm Cancel reply