vbAccelerator - Contents of code file: cToolbarMenu.cls
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
Persistable = 0 'NotPersistable
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "cToolbarMenu"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
' =======================================================================
' FileName: cToolbarMenu
' Author: Steve McMahon
' Date: 8 Feb 2000
'
' Allows menus to pop up and cancel as the user hovers
' over toolbar buttons.
'
'
' Copyright 2000 Steve McMahon
' =======================================================================
Private Enum TRACKINGSTATE '{ // menubar has three states:
TRACK_NONE = 0 ', // * normal, not tracking anything
TRACK_BUTTON ', // * tracking buttons (F10/Alt mode)
TRACK_POPUP '// * tracking popups
End Enum
' Track popup menu constants:
Private m_iTrackingState As TRACKINGSTATE
Private m_bProcessRightArrow As Boolean
Private m_bProcessLeftArrow As Boolean
Private m_hMenuTracking As Long
Private m_iPopupTracking As Long
Private m_bEscapeWasPressed As Boolean
Private m_tPMouse As POINTAPI
Private m_iNewPopup As Long
Private m_bIn As Boolean
Private m_hWnd As Long
Private m_lPtr As Long
Private m_iExit As Integer
Implements ISubclass
Friend Sub CoolMenuAttach(ByRef hWndA As Long, ByVal cBar As cMenuBar)
Dim lPtr As Long
m_iExit = 0
CoolMenuDetach
m_hWnd = hWndA
SendMessageLong m_hWnd, WM_ENTERMENULOOP, 0, 0
AttachMessage Me, m_hWnd, WM_MENUSELECT
m_lPtr = ObjPtr(cBar)
End Sub
Friend Sub CoolMenuDetach()
If (m_hWnd <> 0) Then
SendMessageLong m_hWnd, WM_EXITMENULOOP, 0, 0
DetachMessage Me, m_hWnd, WM_MENUSELECT
m_hWnd = 0
End If
m_hWnd = 0
m_lPtr = 0
End Sub
'/////////////////
'// When user selects a new menu item, note whether it has a submenu
'// and/or parent menu, so I know whether right/left arrow should
'// move to the next popup.
'//
Private Sub MenuSelect(ByVal hMenu As Long, ByVal iItem As Long)
If (m_iTrackingState > 0) Then
'// process right-arrow if item is NOT a submenu
m_bProcessRightArrow = (GetSubMenu(hMenu, iItem) = 0)
'// process left-arrow if curent menu is one I'm tracking
m_bProcessLeftArrow = (hMenu = m_hMenuTracking)
End If
End Sub
'//////////////////
'// Handle menu input event: Look for left/right to change popup menu,
'// mouse movement over over a different menu button for "hot" popup effect.
'// Returns TRUE if message handled (to eat it).
'//
Friend Function MenuInput(m As Msg) As Boolean
Dim iMsg As Long
Dim vKey As Long
Dim tP As POINTAPI
Dim iButton As Long
'ASSERT_VALID(this);
Debug.Assert m_iTrackingState = TRACK_POPUP '; // sanity check
iMsg = m.message
If (iMsg = WM_KEYDOWN) Then
'// handle left/right-arow.
vKey = m.wParam
If ((vKey = vbKeyLeft And m_bProcessLeftArrow) Or _
(vKey = vbKeyRight And m_bProcessRightArrow)) Then
'MBTRACE(_T("CMenuBar::OnMenuInput: handle VK_LEFT/RIGHT\n"));
CancelMenuAndTrackNewOne _
GetNextOrPrevButton(m_iPopupTracking, vKey = vbKeyLeft)
MenuInput = True ' // eat it
' // escape:
ElseIf (vKey = vbKeyEscape) Then
m_bEscapeWasPressed = True '; // (menu will abort itself)
End If
ElseIf (iMsg = WM_MOUSEMOVE Or iMsg = WM_LBUTTONDOWN) Then
'// handle mouse move or click
LSet tP = m.pt
'ScreenToClient m_hWndBand, tP
If (iMsg = WM_MOUSEMOVE) Then
'If (tP.X <> m_tPMouse.X) And (tP.Y <> m_tPMouse.Y) Then
iButton = HitTest(tP)
If IsValidButton(iButton) Then
If iButton <> m_iPopupTracking Then
'// user moved mouse over a different button: track its popup
CancelMenuAndTrackNewOne iButton
End If
End If
LSet m_tPMouse = tP
'End If
ElseIf iMsg = WM_LBUTTONDOWN Then
If (HitTest(tP) = m_iPopupTracking) Then
'// user clicked on same button I am tracking: cancel menu
'MBTRACE(_T("CMenuBar:OnMenuInput: handle mouse click to exit
popup\n"));
CancelMenuAndTrackNewOne -1
MenuInput = True ' // eat it
End If
End If
ElseIf iMsg = WM_LBUTTONUP Or iMsg = WM_RBUTTONUP Then
End If
End Function
Private Function HitTest(pt As POINTAPI) As Long
Dim cBar As cMenuBar
If GetBar(cBar) Then
HitTest = cBar.HitTest(pt)
End If
End Function
Private Property Get IsValidButton(ByVal iButton As Long) As Boolean
If (iButton > 0) Then
IsValidButton = True
End If
End Property
'//////////////////
'// Cancel the current popup menu by posting WM_CANCELMODE, and track a new
'// menu. iNewPopup is which new popup to track (-1 to quit).
'//
Private Sub CancelMenuAndTrackNewOne(ByVal iNewPopup As Long)
Dim cBar As cMenuBar
Dim hMenuPopup As Long
'MBTRACE(_T("CMenuBar::CancelMenuAndTrackNewOne: %d\n"), iNewPopup);
'ASSERT_VALID(this);
If iNewPopup > 0 Then
If (iNewPopup <> m_iPopupTracking) Then
If GetBar(cBar) Then
hMenuPopup = cBar.GetMenuHandle(iNewPopup)
If hMenuPopup <> 0 Then
'PostMessage m_hWndOwner, WM_CANCELMODE, 0, 0 ' // quit menu loop
PostMessage m_hWnd, WM_CANCELMODE, 0, 0
m_iNewPopup = iNewPopup '// go to this popup (-1
= quit)
End If
End If
End If
End If
End Sub
'//////////////////
'// Track the popup submenu associated with the i'th button in the menu bar.
'// This fn actually goes into a loop, tracking different menus until the user
'// selects a command or exits the menu.
'//
Friend Function TrackPopup(ByVal iButton As Long) As Long
Dim nMenuItems As Long
Dim tPM As TPMPARAMS
Dim rcButton As RECT
Dim pt As POINTAPI
Dim hMenuPopup As Long
Dim lR As Long
Dim hwnd As Long
Dim lRtnID As Long
Dim cBar As cMenuBar
If Not m_bIn Then
m_bIn = True
m_iNewPopup = iButton
'Debug.Assert m_hMenu <> 0
If GetBar(cBar) Then
nMenuItems = cBar.Count 'GetMenuItemCount(m_hMenu)
Do While (m_iNewPopup > -1) '// while user selects
another menu
lRtnID = 0
m_iNewPopup = -1 '// assume quit after this
PressButton iButton, True '// press the button
'UpdateWindow ToolbarhWnd(m_hWnd) '// and force repaint
now
SetTrackingState TRACK_POPUP, iButton '// enter tracking state
'// Need to install a hook to trap menu input in order to make
'// left/right-arrow keys and "hot" mouse tracking work.
'//
AttachMsgHook Me
'// get submenu and display it beneath button
GetRect iButton, rcButton
'ClientRectToScreen m_hWndBand, rcButton
tPM.cbSize = Len(tPM)
ComputeMenuTrackPoint rcButton, tPM, pt
'hMenuPopup = GetSubMenu(m_hMenu, iButton)
hMenuPopup = cBar.GetMenuHandle(iButton)
If hMenuPopup <> 0 Then
' Show the menu:
m_hMenuTracking = hMenuPopup
lR = TrackPopupMenuEx(hMenuPopup, _
TPM_LEFTALIGN Or TPM_LEFTBUTTON Or TPM_VERTICAL, _
pt.x, pt.y, m_hWnd, tPM)
'lR is the ID of the menu
lRtnID = lR
End If
'// uninstall hook.
DetachMsgHook
PressButton iButton, False '; // un-press button
'UpdateWindow ToolbarhWNd(m_hWnd) '// and force
repaint now
'// If the user exited the menu loop by pressing Escape,
'// return to track-button state; otherwise normal non-tracking
state.
If (m_bEscapeWasPressed) Then
SetTrackingState TRACK_NONE, iButton
Else
SetTrackingState TRACK_NONE, iButton
End If
'// If the user moved mouse to a new top-level popup (eg from File
to
'// Edit button), I will have posted a WM_CANCELMODE to quit
'// the first popup, and set m_iNewPopup to the new menu to show.
'// Otherwise, m_iNewPopup will be -1 as set above.
'// So just set iButton to the next popup menu and keep looping...
iButton = m_iNewPopup
Loop
' Set hot button if mouse is over, otherwise not:
' The ID of the selected menu
TrackPopup = lRtnID
End If
m_bIn = False
End If
End Function
Private Sub ComputeMenuTrackPoint(ByRef rc As RECT, tPM As TPMPARAMS, tP As
POINTAPI)
tP.x = rc.left
tP.y = rc.bottom
LSet tPM.rcExclude = rc
End Sub
Private Function GetBar(ByRef cBar As cMenuBar) As Boolean
If Not m_lPtr = 0 Then
Set cBar = ObjectFromPtr(m_lPtr)
'Debug.Print "GetBar:OK"
GetBar = True
End If
End Function
Private Sub PressButton(ByVal iButton As Long, ByVal bState As Boolean)
Dim fState As Long
Dim cBar As cMenuBar
If GetBar(cBar) Then
If iButton > 0 And iButton <= cBar.Count Then
cBar.PressButton iButton, bState
End If
End If
End Sub
Private Sub GetRect(ByVal iButton As Long, ByRef tR As RECT)
Dim cBar As cMenuBar
tR.left = 0: tR.top = 0: tR.bottom = 0: tR.right = 0
If GetBar(cBar) Then
If iButton > 0 And iButton <= cBar.Count Then
cBar.GetRect iButton, tR
End If
End If
End Sub
Private Function GetHotItem() As Long
Dim cBar As cMenuBar
If GetBar(cBar) Then
GetHotItem = cBar.HotItem
End If
End Function
Private Function SetHotItem(ByVal iButton As Long) As Long
Dim cBar As cMenuBar
If GetBar(cBar) Then
'Debug.Print "Setting hot item: " & iButton
cBar.HotItem = iButton
End If
End Function
Private Function GetButtonVisible(ByVal iButton As Long) As Boolean
GetButtonVisible = True
End Function
Private Function GetButtonCount() As Long
Dim cBar As cMenuBar
If GetBar(cBar) Then
GetButtonCount = cBar.Count
End If
End Function
Private Sub SetTrackingState(ByVal iState As TRACKINGSTATE, ByVal iButton As
Long)
If (iState <> m_iTrackingState) Then
If (iState = TRACK_NONE) Then
iButton = -1
End If
'#ifdef _DEBUG
' static LPCTSTR StateName[] = { _T("NONE"), _T("BUTTON"), _T("POPUP") };
' MBTRACE(_T("CMenuBar::SetTrackingState to %s, button=%d\n"),
' StateName[iState], iButton);
'#End If
SetHotItem iButton '// could be none (-1)
If (iState = TRACK_POPUP) Then
'// set related state stuff
m_bEscapeWasPressed = False 'FALSE; // assume Esc key not pressed
m_bProcessRightArrow = True '// assume left/right arrow..
m_bProcessLeftArrow = True '; // ..will move to prev/next popup
m_iPopupTracking = iButton '// which popup I'm tracking
End If
m_iTrackingState = iState
End If
End Sub
Private Function GetNextOrPrevButton(ByVal iButton As Long, ByVal bPrev As
Boolean) As Long
Dim iSB As Long
Dim bfound As Boolean
If (bPrev) Then
iSB = iButton
Do While Not bfound
iButton = iButton - 1
If iButton < 1 Then
iButton = GetButtonCount()
End If
If Not (GetButtonVisible(iButton)) Then
If iButton = iSB Then
iButton = -1
Exit Do
End If
Else
bfound = True
End If
Loop
Else
iSB = iButton
Do While Not bfound
iButton = iButton + 1
If (iButton > GetButtonCount()) Then
iButton = 1
End If
If Not GetButtonVisible(iButton) Then
If iButton = iSB Then
iButton = -1
Exit Do
End If
Else
bfound = True
End If
Loop
End If
GetNextOrPrevButton = iButton
End Function
'//////////////////
'// Toggle state from home state to button-tracking and back
'//
Private Sub ToggleTrackButtonMode()
If (m_iTrackingState = TRACK_NONE Or m_iTrackingState = TRACK_BUTTON) Then
If m_iTrackingState = TRACK_NONE Then
SetTrackingState TRACK_BUTTON, 1
Else
SetTrackingState TRACK_NONE, 1
End If
End If
End Sub
Private Property Get ISubclass_MsgResponse() As EMsgResponse
If CurrentMessage = WM_MENUSELECT Then
ISubclass_MsgResponse = emrPreprocess
End If
End Property
Private Property Let ISubclass_MsgResponse(ByVal RHS As EMsgResponse)
'
End Property
Private Function ISubclass_WindowProc(ByVal hwnd As Long, ByVal iMsg As Long,
ByVal wParam As Long, ByVal lParam As Long) As Long
Select Case iMsg
Case WM_MENUSELECT
MenuSelect lParam, (wParam And &HFFFF&)
End Select
End Function
|
|