To create, edit and delete window specific bar menus.
Menu m$()
Menu idx, flags, txt$
state = Form.MenuEnabled Form.MenuEnabled = state
retval = Form.MenuItem(idx) Form.MenuItem(idx) = setval
Menu Kill
txt$ = Form.MenuText(idx) Form.MenuText(idx) = txt$
Sub Form_MenuEvent([index%,] idx%)
Sub Form_MenuOver([index%,] idx%)
m$() | : the string array containing the menu entries |
flags, idx, index | : integer |
retval, setval | : boolean, integer or string |
state | : boolean |
txt$ | : string |
Menu bars can be created in a window by using Menu m$() and, subsequently, edited using the Menu idx, flags, txt$ command or the MenuItem and MenuText properties of the window itself. The enabled status of the menu itself (rather than the individual items) can be controlled using the MenuEnabled property, all menu events are handled by MenuEvent and MenuOver and the menu in the current window can be destroyed by the command Menu Kill.
Creating Menus using Menu m$() Show
Menu m$() displays a menu bar on the screen, where m$() contains the menu title followed by menu entries and an empty string to indicate the end of each menu. The following format must be observed when building a menu:
m$(0) | name of the first menu |
m$(1) | first entry in the first menu |
m$(2) | second entry in the first menu |
m$(n) | last entry in the first menu |
m$(n+1)="" | marks the end of the first menu |
m$(n+2) | name of the second menu |
m$(n+3) | first entry in the second menu |
m$(n+m) | last entry in the second menu |
m$(n+m+1)="" | marks the end of the second menu. |
Along with the text for each menu item, other properties can be set in the original string array as follows:
Should the menus or menu entries be specified in Data lines, the following should be observed:
Do note that Menu m$() only displays the menu bar on the screen. The handling of menu entries is performed by using the event sub <formname>_MenuEvent() or the other Menu command shown below.
NOTE: You can not add sub menus using Menu; to do this, see the relevant section below.
Adding Items and Sub-Menus to Existing Menus Show
Once a menu has been created with Menu, the only way to add new items and sub-menus is by using Windows APIs - NOTE: Items added through APIs do not work with the MenuItem property. See Known Issues.
There are numerous APIs which allow the adding and editing of menu items (see here), but this section will just concentrate on AppendMenu and InsertMenu, which take the following forms:
The meanings of the parameters are:
Finally, before adding a submenu, you need to create a handle to use in the AppendMenu or InsertMenu function, and for this the CreatePopupMenu() API is used.
The following example creates a menu from scratch using APIs with two columns and embeds a sub menu to the second column. Note that the CreatePopupMenu() API is used to create the menu columns as well as submenus.
OpenW 1 : Win_1.AutoRedraw = 1
// Create the main menu and assign it to Win_1
Local Int32 mh = CreateMenu() : ~SetMenu(Win_1.hWnd, mh)
// Create the popup menus for the two columns and the submenu
Local Int32 col1 = CreatePopupMenu(), col2 = CreatePopupMenu(), sm = CreatePopupMenu()
// Add the first column
~AppendMenu(mh, MF_STRING | MF_POPUP, col1, "Column 1")
// Add the items for the first column
~AppendMenu(col1, MF_STRING, 1, "Item &1")
~AppendMenu(col1, MF_STRING, 2, "Item &2")
~AppendMenu(col1, MF_STRING, 3, "&Close")
// Add the second column
~AppendMenu(mh, MF_STRING | MF_POPUP, col2, "Column 2")
// Add the first item for the second column
~AppendMenu(col2, MF_STRING, 11, "Item &3")
// Add the submenu to the second column
~AppendMenu(col2, MF_STRING | MF_POPUP, sm, "SubMenu")
// Add submenu items
~AppendMenu(sm, MF_STRING, 21, "Item &7")
~AppendMenu(sm, MF_STRING, 22, "Item &8")
~AppendMenu(sm, MF_STRING, 23, "Item &9")
// Add the last item to column two
~AppendMenu(col2, MF_STRING, 14, "Item &6")
// Now insert the missing Items 4 and 5 into Column 2
~InsertMenu(col2, sm, MF_STRING, 12, "Item &4")
~InsertMenu(col2, 14, MF_STRING, 13, "Item &5")
~DrawMenuBar(Win_1.hWnd)
Do : Sleep : Until Win_1 Is Nothing
Sub Win_1_MenuEvent(idx%)
If idx% = 3 Then Win_1.Close
EndSub
Note the DrawMenuBar() API at the end: this must be used to display any changes that you have made.
Editing Menu Item Properties using Menu idx, flags, txt$ Show
Once the menu has been created, Menu idx, flags, txt$ can be used to modify a specific menu item, whether created by Menu m$() or API.
The idx is identical to the number you give with #number, #menu_entry, or, when omitted, with the index of the array if created by Menu m$(), or NewItemID if created by API.
With flags you can specify attributes which define both content and state, attributes of different types being combined with | (i.e., the Or symbol).
Finally, txt$ contains the menu item description. Please note that this must always be included (except for Bitmaps and breaks) as leaving this parameter as a null string ("") will result in the menu entry description becoming blank.
The attributes defining content or type are:
In addition to these, the following affect the state of the menu entry.
Viewing and Setting Menu Item Properties using MenuItem(idx) Show
Another way to modify a menu item is to use the MenuItem collection, a property of the Form object. The collection is made up of all menu items created using the Menu m$() command and is accessed using the syntax Form.MenuItem(idx).Property/Method where idx is the menu item's index number. MenuItem was designed as a OCX wrapper for the (older) command Menu idx, flags, txt$ but, unlike that command, it does not work with menu items created with Windows APIs rather than Menu m$() - see Known Issues below for more details.
As well as editing, the MenuItem properties can be used to discover the current state of menu items in the MenuItem collection.
MenuItem has eight properties which are:
Index | Long | Sets or gets the ID value for the menu item. |
Text | String | Sets or gets the menu entry text. |
Tag | String | Sets and gets the tag string for the menu item. |
Style | Long | Sets and gets the style for the menu item. Can be one of the following predefined constants: basmBarBreak ($20) basmBreak ($40) basmRadio ($200) basmRight ($4000) basmSeparator ($800) |
State | Long | Sets and gets the state for the menu item. Can be one of the following predefined constants: basmChecked ($8) basmDefault ($1000) basmDisabled ($3) basmGrayed ($3) basmHiLite ($80) |
Enabled | Bool | Sets and gets the enabled style for the menu item. |
Checked | Bool | Sets and gets the checked style for the menu item. |
Default | Bool | Sets and gets the default menu item. |
In addition, it also has the following method:
Remove Deletes the menu item.
Viewing and Setting Menu Item Properties using APIs Show
There is a simple API replacement for the Menu idx, flags, txt$ command in ~ModifyMenu(hMenu, Position, Flags, ItemID, ItemData) where the meanings of the parameters are:
Similarly, there is an easy way of interrogating the status of a menu item by using the state = GetMenuState(hMenu, ItemID, Flags) API, whose parameters are the same as above except for Flags which is either MF_BYCOMMAND if ItemID refers to the menu item ID no, or MF_BYPOSITION if it refers to the menu item's physical position.
However, there is no simple replacement for the ease with which MenuItem allows you to interrogate the properties of each menu item; however, as that avenue is not open to any menu item created using an API, a more long-winded process using the GetMenuItemInfo(hMenu, Position, Flags, in MenuItem) API must be used instead.
Rather than delving into the intricacies of this API (see here for more details), using the two procedures in the example below which mimic the functionality of the MenuItem collection is a far simpler solution. Note that, while both these procedures work with all menu items, the SetMenuItem function does not set the internal GB32 MenuItem properties so, for menu items created with Menu m$() for which you wish to continue using the MenuItem colection, it is best to use that to set internal properties instead.
OpenW 1 : Win_1.AutoRedraw = 1
Local m$(5) : m$(0) = "#1#File" : m$(1) = "#2#Open" : m$(2) = "#3#Save" : m$(3) = "-" : m$(4) = "#4#Backup" : m$(5) = ""
Menu m$()
Print "Item 3 - Text = " & GetMenuItem( , 3, "text")
SetMenuItem(, 3, "index", 7) // Change ID No of item 3 to 7
Print "Item 7 - Text = " & GetMenuItem( , 7, "text")
SetMenuItem(, 4, "checked", True) // Set item 4 to checked
Print "Item 4 checked? = " & GetMenuItem( , 4, "checked")
Do : Sleep : Until Win_1 Is Nothing
Function GetMenuItem(Optional mForm As Variant, index%, dType$)
// James Gaite 13th December 2015
dType$ = Lower(dType$) : If IsMissing(mForm) Then Set mForm = Me
Local a$, ec%, hMenu% = GetMenu(mForm.hwnd), mii As MENUITEMINFO
If dType$ = "text" : Return mForm.MenuText(index%)
Else
mii.Size = SizeOf(MENUITEMINFO) : mii.Mask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_DATA
ec% = GetMenuItemInfo(hMenu%, index%, False, mii)
If dType$ = "style" : Return mii.Type
ElseIf dType$ = "state" : Return mii.State
ElseIf dType$ = "index" : Return mii.ID
ElseIf dType$ = "enabled" Then Return mii.State <> (mii.State | basmDisabled)
ElseIf dType$ = "default" Then Return mii.State = (mii.State | basmDefault)
ElseIf dType$ = "checked" Then Return mii.State = (mii.State | basmChecked)
EndIf
EndIf
EndFunction
Procedure SetMenuItem(Optional mForm As Variant, index%, dType$, dData As Variant)
// James Gaite 13th December 2015
Type MENUITEMINFO
- Long Size, Mask, Type, State, ID, SubMenu, bmpChecked, bmpUnchecked, ItemData, TypeData, cch, bmpItem
EndType
Global Const MIIM_BITMAP = $0080, MIIM_FTYPE = $0100, MIIM_STRING = $0040
Global Const MIIM_ALL = MIIM_STATE | MIIM_ID | MIIM_SUBMENU | MIIM_CHECKMARKS | MIIM_DATA | MIIM_STRING | MIIM_BITMAP | MIIM_FTYPE
dType$ = Lower(dType$) : If IsMissing(mForm) Then Set mForm = Me
Local a$, ec%, hMenu% = GetMenu(mForm.hwnd), mii As MENUITEMINFO
If dType$ = "text" : mForm.MenuText(index%) = dData
Else
mii.Size = SizeOf(MENUITEMINFO) : mii.Mask = MIIM_TYPE | MIIM_STATE | MIIM_ID | MIIM_DATA
ec% = GetMenuItemInfo(hMenu%, index%, False, mii)
If dType$ = "style" : mii.Type = dData : mii.Mask = MIIM_FTYPE : ec% = 0
ElseIf dType$ = "state" : mii.State = dData : mii.Mask = MIIM_STATE : ec% = 0
ElseIf dType$ = "index" : mii.ID = dData : mii.Mask = MIIM_ID : ec% = 0
Else
mii.Mask = MIIM_STATE
If dType$ = "enabled" : If dData Then ec% = 0 : mii.State = mii.State | basmDisabled Else mii.State = mii.State Xor basmDisabled
ElseIf dType$ = "default" : If dData Then ec% = 0 : mii.State = mii.State | basmDefault Else mii.State = mii.State Xor basmDefault
ElseIf dType$ = "checked" : If dData Then ec% = 0 : mii.State = mii.State | basmChecked Else mii.State = mii.State Xor basmChecked
EndIf : EndIf
EndIf
If ec% = 0 Then ec% = SetMenuItemInfo(hMenu%, index%, False, mii)
~DrawMenuBar(mForm.hwnd)
EndProcedure
Viewing and Setting Menu Labels using MenuText(idx) Show
A quick way of viewing or setting a menu item's label text is by using MenuText(idx), where id is the menu item's index number. Unlike MenuItem, MenuText will work with all menu items, regardless of how they were created.
Note that MenuText returns the ampersand('&') hotkey marker in the text (but no other formatting mark) which may or may not be desirable. See the main example below for a method of removing this.
Handling Menu Events Show
Menu events are handled internally by GB32 in the following way:
Each time the user moves the highlighting from one item to another, the system sends a WM_MENUSELECT message to the window procedure of the menu's owner window. This message invokes the MenuOver event sub, identifying the currently selected menu item in idx%. Many applications provide an information area at the bottom of their main windows and use this message to display additional information about the selected menu item.
When the user chooses a command item from a menu, the system sends a WM_COMMAND message to the window procedure. When this message is from a menu, GFA-BASIC 32 invokes the MenuEvent event sub. The idx% parameter contains the identifier of the chosen item. The window procedure should examine the identifier and process the message accordingly.
There is one further event - OnMenuHelp - which is invoked by hovering the mouse over a menu item and pressing 'F1'. For more information, see this page.
When the Form to which the menu is attached forms part of an array, the optional parameter index% specifies the number of the Form.
Removing Items from Menus Show
There are two methods that can be used to remove menu items: the MenuItem.Remove(idx%) method, or the ~RemoveMenu(hMenu, Position, Flags).
The first of these can only be used with menu items created by Menu and is dealt with here.
The RemoveMenu() API can be used for all menu items and its parameters are:
To see how this works, insert the following lines into the example here, just before the call to the DrawMenuBar() API.
// Removes Item 9 from sub menu
~RemoveMenu(mh, 23, MF_BYCOMMAND)
Disabling, Enabling and Destroying Menus Show
The sections above explain how to enable or disable individual menu items; however, if you wish to disable (or re-enable) the entire menu, then you can use the MenuEnabled property of the respective form which contains the menu. To disable, set MenuEnable to False; to re-enable, set it to True.
When a form is closed, GB32 will destroy any menu assigned to that window at the same time; however, if you wish to remove a menu from a window or form and yet keep that window open, you can use the Menu Kill command to do just this. Note, however, that Menu Kill destroys the menu in the current window (there is no way of specifying an alternative) so ensure that the correct window or form has focus before calling this command.
The example below creates a basic Menu and shows how items can be changed and events handled using standard GB32 commands. Show
Data Title &1,"#99#Entry &1", Entry &2, &End,
Data Title &2, Entry &3, Entry &4, Entry &5,
Data Title &3, Entry &6, Entry &7, Entry &8, */
// Create the array with which the menu will be formed
Dim m$(20), i% = -1
Do
i% ++
Read m$(i%)
Until InStr(m$(i%), "*/")
m$(i%) = ""
// Open the Form and create a MenuBar
OpenW # 100
AutoRedraw = 1
Menu m$()
// Create a StatusBar to show events
Ocx StatusBar stb
stb.Add , , "" : stb.Panel(1).AutoSize = 2 // For MenuOver
stb.Add , , "" : stb.Panel(2).AutoSize = 2 // For MenuEvent
stb.Add , , "" : stb.Panel(3).AutoSize = 2 // For OnMenuHelp
// Show Changes to MenuItem(99)
Dim mi As MenuItem
Set mi = Me.MenuItem(99)
FontBold = True : Print AT(1, 5); "Before Change:" : FontBold = False
Print AT(1, 6); "MenuItem(99).Default ="; mi.Default
Print AT(1, 7); "MenuItem(99).Text = "; mi.Text
Form(100).MenuItem(99).Default = True
Menu 99, MF_STRING, "Item &99"
FontBold = True : Print AT(1, 9); "After Change:" : FontBold = False
Print AT(1, 10); "MenuItem(99).Default ="; mi.Default
Print AT(1, 11); "MenuItem(99).Text = "; mi.Text
// Command Button to Disable/Enable Menu
Ocx Command cmd = "Disable Menu", 10, 10, 120, 22
Do
Sleep
Loop Until Me Is Nothing
Sub cmd_Click
If Left(cmd.Caption, 7) = "Disable"
Form(100).MenuEnabled = False : cmd.Caption = "Enable Menu"
Else
Form(100).MenuEnabled = True : cmd.Caption = "Disable Menu"
EndIf
EndSub
Sub Form_MenuOver(Index%, idx%)
Local a$ = Me.MenuText(idx%) : reSub a$, "&", ""
stb.Panel(1).Text = (idx > 0 ? "MenuOver: " & a$ : "")
EndSub
Sub Form_MenuEvent(Index%, idx%)
Local a$ = Me.MenuText(idx%), n% : reSub a$, "&", ""
stb.Panel(2).Text = "Selected Item: " & a$
// Ticks unchecked item or removes tick from checked items
Me.MenuItem(idx%).Checked = 1 + Form(100).MenuItem(idx%).Checked
// Note MENU(0) is the same as idx%
If MENU(0) = 3 Then Form(100).Close // Close if 'End' selected
EndSub
Sub Form_OnMenuHelp(Index%, idx%, x%, y%)
Local a$ = Me.MenuText(idx%) : reSub a$, "&", ""
stb.Panel(3).Text = (idx% > 0 ? "Help Requested for: " & a$ : "")
EndSub
This second example from the original German GFA help file, is a good illustration of how to mix standard GB32 commands and Windows APIs to create and alter menus. Show
// A Menu created using Data Lines plus a Sub Menu created using API
Mode StrSpace 0 // No leading spaces for strings
Global i%, j% = 0, a$, men$(20), p%, hm%
OpenW 1
Data 0,MenuTest,1001,Test1,1002,Test2,1003,Test3,0,-,1004,Ende,0,*
Do
Read i%, a$
Exit Do If a$ = "*"
If i% > 0 Then men$(j%) = "#" & i% & "#" & a$ Else men$(j%) = a$
Inc j%
Loop
Menu men$()
'**** Inserting the Sub-Menu ****
hm% = GetMenu(Win_1.hWnd) : p% = CreatePopupMenu()
~AppendMenu(p%, MF_STRING, 700, "A1")
~AppendMenu(p%, MF_STRING, 701, "A2")
~ModifyMenu(hm%, 1002, MF_STRING | MF_POPUP, p%, "Test2")
'************************
Do
Sleep
Until Me Is Nothing
Sub Win_1_MenuEvent(i%) // For all menu items
Local x%
Switch i%
Case 1004
Win_1.Close
'*************************
// Check or uncheck items created with ~AppendMenu
Case 700
x% = GetMenu(Win_1.hWnd)
If GetMenuState(x%, 700, 0 ) And MF_CHECKED
~ModifyMenu( x%, 700, MF_STRING, 700, "A1")
Else
~ModifyMenu( x%, 700, MF_CHECKED | MF_STRING, 700, "A1")
EndIf
Case 701
x% = GetMenu(Win_1.hWnd)
If GetMenuState(x%, 701, 0 ) _
And MF_CHECKED
~ModifyMenu( x%, 701, MF_STRING, 701, "A2")
Else
~ModifyMenu( x%, 701, MF_CHECKED | MF_STRING, 701, "A2")
EndIf
'************************
// Check or uncheck items created by Menu
x% = Win_1.MenuItem(i%).State
If x%
Win_1.MenuItem(i%).Checked = 0
Else
Win_1.MenuItem(i%).Checked = -1
EndIf
EndSwitch
EndSub
As noted above, the MenuItem property of the Form hosting the menu does not work with menu items added through Windows APIs and this is because the MenuItem collection is an internal collection formed by GFA Basic which is created at the same time as the Menu and there is no programmatical means available to add to this collection once it has been created. See this article on Sjouke Hamstra's blog for a more detailed and technical explanation of this issue.
{Created by Sjouke Hamstra; Last updated: 20/12/2015 by James Gaite}