Find out when a user has highlighted a menu option

By | 2019-09-19

I have posted this article primarily for newbies but I’m sure others could benefit as well. I did a quick search and finding nothing on it on the forum decided to post it as an article. I got the idea to write this article recently when I was standing in for a colleague teaching programmng to some college students. Some of the students who were working on a course assignment wanted to know if it was possible in VB to tell when a user has highlighted a menu option and show a description in the status bar.

If you are wondering the same thing, I am glad to say that it is possible. If you are like me you would have been disappointed at finding no Menu_Mousexxxx events in VB. Well if you read the Windows documentation you will find that Windows communicates with programs via messages. With VB, you don’t have to know anything about these messages because it translates them into events for you. This is convenient except on those rare occasions when you want to know when the window is moved, the computer is entering power saving mode, or as in this case, when the mouse hovers over a menu item. To get this info you have to intercept the messages and do the appropriate thing.

The tools you will need for this are a few API functions and constants. You will have to subclass the window and implement a callback function. If you don’t know what either subclassing is or what a callback is then do not alter the code in anyway  I am working on uploading an ActiveX DLL that will make it easy and safe to subclass as many windows as you want.

Every window has a default window procedure that knows how to do all the general stuff you have never written code for such as minimize, maximize etc. You  need to replace this function with your own that will do what you want and delegate to the default window procedure in other cases. Here is the code that does it. Create a standard module called SubClass.Bas and a form frmMain with a status bar sbrMain. We will check out some of the items in the control box.

Option Explicit
'API constants
Private const GWL_WNDPROC = -4
Private const WM_MENUSELECT = &H11F
Private const WM_ENTERMENULOOP = &H211
Private const WM_EXITMENULOOP = &H212

'Declares
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 GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As Long, ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag 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

'other private items
private lnghWnd as Long          'the handle of the window
private oldProcAddr as long      'address of the widow procedure

'this is the function Windows will call when this window receives a message
'do not call it yourself even if you want to send a message to the window,
'in that case use the SendMessage API
Private Function WndProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wparam As Long, ByVal lparam As Long) As Long
  'to keep us on the safe side let's send the message to the original window procedure
  WndProc = CallWindowProc(oldprocaddrr, hWnd, uMsg, wpara, , lparam)
  'now check if it is our message
  Select Case uMsg
  Case WM_ENTERMENULOOP
    frmMain.sbrMain.Style = sbrSimple
  Case WM_EXITMENULOOP
    frmMain.sbrMain.Style = sbrNormal
  Case WM_MENUSELECT
    'the menu id is in the low-order word of wParam, the handle is in lParam
    Dim mnuId As Long, Length As Long, mnuCaption As String
    mnuId = (wparam AND &HFFFF&)
    'get menu caption
    mnuCaption = Space$(256)
    Length = GetMenuString(lparam, mnuId, mnuCaption, Len(mnuCaption), 0)
    mnuCaption = Left$(mnuCaption, Length)
    'find out which menu item it was
    With frmMain.sbrMain
      Select Case mnuCaption
      Case "&Restore"
        .SimpleText = "Restore window to original size"
      Case "&Move"
        .SimpleText = "Move the window using the keyboard"
      Case "&Close" & vbTab & "Alt+F4"
        .SimpleText = "Quit this application"
      Case "Ma&ximize"
        .SimpleText = "Fadz is fabulous - fill screen with this window"
      Case Else
        .SimpleText = mnuCaption
      End Select
    End With
  End Select
End Function

'thi is called by the subclassed window
Public Sub StartSubClassing(ByVal hWnd As Long)
  lnghWnd = hWnd
  oldProcAddr = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WndProc)
End Sub

Public Sub StopSubClassing()
  SetWindowLong lnghWnd, GWL_WNDPROC, oldProcAddr
End Sub

In the from frmMain, enter the following code

Option Explicit
Private Sub Form_Initialize()
StartSubClassing Me.hWnd 'you can use Load if you wnat
End Sub

Private Sub Form_Terminate()
StopSubClassing 'if you don't do this you will crach
End Sub

That’s how you do it. Of course,you should add handling for menus that I didn’t include and for my code to work add for the ones I included. The text you test for should contain the “&” ampersands because that is how windows will return them.

If crashing is one of your project requirements then go ahead and leave out the code in Form_terminate. Similarly for the code to work flawlessly do not end you prgram using the end statement, the End command bar button or menu item in the IDE. These will prevent a proper unload of your form and you will crash. With this in mind, save your code b4 running the program.

Author: dwirch

Derek Wirch is a seasoned IT professional with an impressive career dating back to 1986. He brings a wealth of knowledge and hands-on experience that is invaluable to those embarking on their journey in the tech industry.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.