Microsoft KB Archive/216185

= How To Create Multi-Column Menus in Visual Basic Using the WIN32 API =

Article ID: 216185

Article Last Modified on 7/15/2004

-

APPLIES TO


 * Microsoft Visual Basic 5.0 Learning Edition
 * Microsoft Visual Basic 6.0 Learning Edition
 * Microsoft Visual Basic 5.0 Professional Edition
 * Microsoft Visual Basic 6.0 Professional Edition
 * Microsoft Visual Basic 5.0 Enterprise Edition
 * Microsoft Visual Basic 6.0 Enterprise Edition

-



This article was previously published under Q216185



SUMMARY
Multi-column menus can be useful when you don't want to cover up sections of your form or when handling large menus that would otherwise extend beyond the screen. Visual Basic offers no built-in method for creating multi-column menus. However, you can split a menu into multiple columns by using a few API calls. The following sample demonstrates how to do this both at Form Load and dynamically, from a CommandButton.

NOTE: This technique will only work for menus that are visible on the menu bar. It does not work for invisible menus, such as shortcut or pop-up menus.



Step by step example
 Open a new Standard EXE Project. Form1 is created by default. Add a CommandButton to Form1. On the Tools menu, click Menu Editor. Create a menu consisting of at least two top level menus containing at least four submenu items each.  Add the following code to the General Declarations section of Form1:

Option Explicit

Private Type MENUITEMINFO cbSize As Long fMask As Long fType As Long fState As Long wID As Long hSubMenu As Long hbmpChecked As Long hbmpUnchecked As Long dwItemData As Long dwTypeData As String cch As Long End Type

Private Const MF_MENUBARBREAK = &H20& ' columns with a separator line Private Const MF_MENUBREAK = &H40&   ' columns w/o a separator line Private Const MF_STRING = &H0& Private Const MF_HELP = &H4000& Private Const MFS_DEFAULT = &H1000&

Private Const MIIM_ID = &H2 Private Const MIIM_SUBMENU = &H4 Private Const MIIM_TYPE = &H10 Private Const MIIM_DATA = &H20

Private Declare Function GetMenu Lib "user32" (ByVal hwnd As Long) As Long

Private Declare Function GetMenuItemInfo Lib "user32" _ Alias "GetMenuItemInfoA" _ (ByVal hMenu As Long, ByVal un As Long, ByVal B As Boolean, _  lpMenuItemInfo As MENUITEMINFO) As Long Private Declare Function SetMenuItemInfo Lib "user32" _ Alias "SetMenuItemInfoA" _ (ByVal hMenu As Long, ByVal un As Long, ByVal bool As Boolean, _  lpcMenuItemInfo As MENUITEMINFO) As Long

Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) _ As Long

Private Declare Function GetSubMenu Lib "user32" (ByVal hMenu As Long, _  ByVal nPos As Long) As Long

Private Sub Command1_Click ' Splitting a menu here demonstrates that this can be done dynamically. Dim mnuItemInfo As MENUITEMINFO, hMenu As Long, hSubMenu As Long Dim BuffStr As String * 80  ' Define as largest possible menu text.

hMenu = GetMenu(Me.hwnd)  ' retrieve menu handle. BuffStr = Space(80) With mnuItemInfo  ' Initialize the UDT. .cbSize = Len(mnuItemInfo)  ' 44 .dwTypeData = BuffStr & Chr(0) .fType = MF_STRING .cch = Len(mnuItemInfo.dwTypeData)  ' 80 .fState = MFS_DEFAULT .fMask = MIIM_ID Or MIIM_DATA Or MIIM_TYPE Or MIIM_SUBMENU End With ' Use item break point position for the '3' below (zero-based list). hSubMenu = GetSubMenu(hMenu, 0) If GetMenuItemInfo(hSubMenu, 2, True, mnuItemInfo) = 0 Then MsgBox "GetMenuItemInfo failed. Error: " & Err.LastDllError,, _ "Error" Else mnuItemInfo.fType = mnuItemInfo.fType Or MF_MENUBARBREAK If SetMenuItemInfo(hSubMenu, 2, True, mnuItemInfo) = 0 Then MsgBox "SetMenuItemInfo failed. Error: " & Err.LastDllError,, _ "Error" End If  End If   DrawMenuBar (Me.hwnd)   ' Repaint top level Menu. End Sub

Private Sub Form_Load ' This works for either an API-created menu or a native VB Menu. Dim mnuItemInfo As MENUITEMINFO, hMenu As Long, hSubMenu As Long Dim BuffStr As String * 80  ' Define as largest possible menu text.

hMenu = GetMenu(Me.hwnd)  ' Retrieve menu handle. BuffStr = Space(80) With mnuItemInfo  ' Initialize the UDT .cbSize = Len(mnuItemInfo)  ' 44 .dwTypeData = BuffStr & Chr(0) .fType = MF_STRING .cch = Len(mnuItemInfo.dwTypeData)  ' 80 .fState = MFS_DEFAULT .fMask = MIIM_ID Or MIIM_DATA Or MIIM_TYPE Or MIIM_SUBMENU End With ' Use item break point position for the '3' below (zero-based list). hSubMenu = GetSubMenu(hMenu, 1) If GetMenuItemInfo(hSubMenu, 2, True, mnuItemInfo) = 0 Then MsgBox "GetMenuItemInfo failed. Error: " & Err.LastDllError,, _ "Error" Else mnuItemInfo.fType = mnuItemInfo.fType Or MF_MENUBREAK If SetMenuItemInfo(hSubMenu, 2, True, mnuItemInfo) = 0 Then MsgBox "SetMenuItemInfo failed. Error: " & Err.LastDllError,, _ "Error" End If  End If   DrawMenuBar (Me.hwnd)   ' Repaint top level Menu. End Sub

Private Sub Form_MouseDown(Button As Integer, Shift As Integer, X As Single, Y As Single) If Button = vbRightButton Then PopupMenu Aaa  ' Use the name of one of your menus. End If End Sub  Change the "Aaa" in the last procedure above to the name of one of your top level menus. Run the project and click on the menu items.</ol>

Results: The first menu drops straight down as usual, but the second has two columns. Also, right-click the Form and the first item comes up as a normal pop-up menu. Then click the CommandButton and try these tests again. Now the first menu item and the pop-up have two columns with a separator line between them. If you wish to dynamically divide the columns, you can use the API function GetMenuItemCount to get the number of items and then loop through them. You can either search for a specific string to determine the break point or you can just break every X items.

<div class="references_section">