Liberty Basic is develeopped by Carl Gundel
Original Newsletter compiled by Alyce Watson and Brosco
Translation to HTML: Raymond Roumeas

The Liberty Basic Newsletter - Issue #45 - JUL 99

"Knowledge is a gift we receive from others." - Michael T. Rankin

In this issue:

Menus - Part 2

In future issues:


Menu API calls: advanced

Add/Remove SubMenus and Menu Items.

Make popup menu columns.

Track popup menus.

Adding a Most Recently Used list to a menu.


Menu API calls: advanced

Add/Remove SubMenus and Menu Items.

Menu Items may be removed with a call to DeleteMenu. This will not work with SubMenus. A Menu Item that has been DELETED may be INSERTED again later.

[delete.menu.item]
calldll #user, "DeleteMenu",_
hSubMenu as word,_ 'handle of SubMenu
menu.id as short,_ 'ID of menu item to delete
_MF_BYCOMMAND as word,_ 'use ID rather than position
result as ushort

Menu Items and SubMenus may be removed permanently with a call to RemoveMenu. Remember that you must make a call to DrawMenu to redraw the menu bar, anytime you make modifications to the Menu Bar or SubMenus:

[remove.sub.menu]
calldll #user, "RemoveMenu",_
hMenu as word,_ 'handle of Menu Bar
hSubMenu as short,_ 'handle of SubMenu to remove
_MF_BYCOMMAND as word,_ 'flags
result as ushort
 
calldll #user, "DrawMenuBar",_
hWnd as word,_ 'handle of window containing Menu Bar
result as void
 
[remove.menu.item]
calldll #user, "RemoveMenu",_
hSubMenu as word,_ 'handle of SubMenu
menu.id,_ 'handle of Menu Item to remove
_MF_BYCOMMAND as word,_ 'flags
result as ushort

YOU MAY ONLY INSERT MENU ITEMS THAT WERE DELETED PREVIOUSLY with a call to DeleteMenu. You may NOT insert Menu Items that were deleted with RemoveMenu. You may NOT insert items that were not initialized with Liberty BASIC MENU commands.

If you do not want an item to be included at the start of a program, you must list it in your LB menu and call DeleteMenu at the start of your program. When the item is needed, then a call is made to InsertMenu. Note that you also need to specify the string for the Menu Item, even if you named it earlier with LB commands. This technique will be used again later in this document. This call only works with Menu Items, not with SubMenus.

[insert.menu.item]
Menu.flags=_MF_STRING or _MF_BYCOMMAND
 
calldll #user, "InsertMenu",_
hSubMenu as word,_ 'handle of SubMenu
menu.id as short,_ 'ID of item to insert
Menu.flags as word,_ 'flags
menu.id as word,_ 'ID of item to insert
"Item Title" as ptr,_ 'the text string for the Menu Item
results as ushort


Make popup menu columns.

Liberty BASIC provides us with an easy way to make a horizontal dividing line within our popup menus. We can call on the API to divide menus into columns - either with or without a visible dividing line. This again uses a call to ModifyMenu. It requires that we have used LB commands to specify a Menu Item, which we then transform into the column break we need. We can have multiple columns if we insert multiple breaks. The flag used in the call indicates whether there will be a visible dividing line between columns.

If the flag is _MF_MENUBARBREAK columns are formed with lines between. If the flag is _MF_MENUBREAK columns are formed without lines between.

[make.menu.columns.dividing.lines]
FLAGS = _MF_BYCOMMAND OR _MF_STRING OR _MF_MENUBARBREAK
calldll #user, "ModifyMenu",_
hSubMenu as word,_ 'handle of submenu
menu.id as word,_ 'ID of Menu Item
FLAGS as word,_ 'flags
menu.id as word,_ 'ID of Menu Item
"" as ptr,_ 'no string needed here!
results as ushort
 
[make.menu.columns.noline]
FLAGS = _MF_BYCOMMAND OR _MF_STRING OR _MF_MENUBREAK
calldll #user, "ModifyMenu",_
hSubMenu as word,_ 'handle of submenu
menu.id as word,_ 'ID of Menu Item
FLAGS as word,_ 'flags
menu.id as word,_ 'ID of Menu Item
"" as ptr,_ 'no string needed here!
results as ushort


Track popup menus.

Once you have initialized a menu with LB commands, you can have the popup menu appear on the screen anywhere you like - even outside of the program's window! This procedure requires a call to TrackPopupMenu. You can even remove the Menu Bar or the SubMenu on the Menu Bar and still have access to the popup menu. This is most frequently activated in applications by a click of the right mouse button, but the choice is up to the programmer.

There is one tricky part about positioning the popup menu. Since it can appear outside of a window, the X, Y coordinates used to position it are SCREEN coordinates, not CLIENT coordinates. First we must make a call to ClientToScreen, which converts the client coordinates of any point on the screen to screen coordinates. In the following example, the POINTAPI.x.struct is filled by the ClientToScreen call with the screen position of the left side of the window.

If you want your popup to appear where the mouse is located, just add MouseX to this X position, as in the example below.

The same is true for the Y position. The POINTAPI.y.struct variable is filled with the coordinate of the upper side of the window. Add this to MouseY to have the popup menu appear at the current Y position of the mouse.

[track.popup]
struct POINTAPI,_
x As short,_
y As short
 
calldll #user, "ClientToScreen",_
hWnd As word,_ 'handle of window
POINTAPI as struct,_ 'struct to be filled by the call
r as void
 
'*****open menu at mouse coords:
mx = POINTAPI.x.struct+MouseX
my = POINTAPI.y.struct+MouseY
 
calldll #user, "TrackPopupMenu",_
hSubMenu as word,_ 'handle of SubMenu (popup menu)
0 as word,_ 'flag for alignment - this is the default
mx as word,_ 'upper left x position for menu
my as word,_ 'upper left y position for menu
0 as word,_ 'reserved - always 0
hWnd as word,_ 'handle of window
0 as long,_ 'not used in LB
r as ushort


Adding a Most Recently Used list to a menu.

Many applications append a list of most recently opened files to the bottom of the File Menu. There is a way to do this in Liberty BASIC. It is not possible to call AppendMenu successfully in LB, because there is no way to associate the new Menu Item with a branch label within the program.

It can be done however, by setting up the File Menu with dummy items where the recently opened files will later appear. You must get the ID's of these items and call DeleteMenu for each of them. After a file is opened by the user of your program, a call is made to InsertMenu, adding one of the dummy items back and changing its name to be the name of the file. If you have already reinserted all dummy items, then you will need to replace a Menu Item with the new file name with a call to ModifyMenu.

The branch label associated with the Menu Item ID must contain contain or access the procedure that utilizes the file.

This would work as follows. The menu would contain one or more dummy items:

menu #win, "&File", "&Open",[open],"Dummy One",[file.one]

When the user chooses the option to Open a file, a filedialog would allow him/her to choose a file to open:

filedialog "Choose file", "*.*", fileName$

You would then call InsertMenu to insert a previously deleted dummy item:

Menu.flags=_MF_STRING or _MF_BYCOMMAND
calldll #user, "InsertMenu",_
hSubMenu as word,_ 'handle of SubMenu
menu.id as short,_ 'ID of item to insert
Menu.flags as word,_ 'flags
menu.id as word,_ 'ID of item to insert
fileName$ as ptr,_ 'the text string for the Menu Item
results as ushort

After this, the program would continue on to the branch label that utilizes the chosen file:

GOTO [file.one]

This method was "invented" by Alyce. It works. It has some drawbacks, though. The files are added back in order of menu item placement, so if many files are opened, the most recently opened file might actually appear in the middle or at the bottom of the list. The other problem is the length of the file name. Professional applications truncate this name if it is long. You can see why, if you use the sample program to open a file that is buried several directories deep. It makes for a pretty wide popup menu!

Brosco has solved these problems with his MRU menu list set of reusable subroutines.

After the user chooses a file, the path and filename should be truncated for a more professional look.

MaxLen=20
if len(fileName$) > MaxLen then
fileName$ = left$( fileName$, 3) + " ... " + _
right$( fileName$, MaxLen - 8)
end if

The fileName$ "c:\lb14w\programs\brosco\samples\just4fun.bas" would instead become "c:\...just4fun.bas"

The next operation to perform is to see if the fileName$ is already on the menu list. To make this easy, the list is maintained in an array. If the name is on the list, and is in the first position, then no other operation needs to be performed. If it is on the list, but not in the first position, the list must be rearranged to bring the fileName$ to the first postion and move the other files into their new positions. If it is not on the list, it must be added in place of the oldest file$ on the list, if the list is full.

If the list is not full, then a previously deleted dummy Menu Item must be replaced with a call to InsertMenu. The string$ of this inserted Menu Item will be the truncated fileName$ and it will be placed at the top of the list - as the first item in the list array.

Whew! It gets pretty complicated! If you don't care to puzzle out the exact code to perform these operations, you don't need to do so. Brosco has written a set of reusuable subroutines that you can cut and paste into your program. There is a section for you to initialize the routines with your own values, such as position in the menu of the first MRU item, number of MRU items to list and maximum length for file names. See "menumru.bas," which is included at the end of this document.

One last note: upon closing of the program, you may write the file names from the list into an ini file that will be read when the program is next opened, and used to fill the MRU list. If you choose to do this, be sure to use COMPLETE filenames in the ini file, not the truncated versions!

 MENUMRU.BAS
' Demo of Menu MRU (Most Recently Used) list
' Original code by Alyce Watson
' Reusable subroutine by Brosco
' Version 1.00 - May 1998
'
NOMAINWIN
UpperLeftX = 20
UpperLeftY = 20
WindowWidth = 600 : WindowHeight = 300
 
Menu #MenuList, "File", _
"Open",[open], _ ' SubMenuItem 0
"Exit", [Menu.List.Exit], _ ' SubMenuItem 1
|, _ ' SubMenuItem 2
"Item One",[menu.one], _ ' SubMenuItem 3
"Item Two",[menu.two], _ ' SubMenuItem 4
"Item Three",[menu.three], _ ' SubMenuItem 5
"Item Four",[menu.four] ' SubMenuItem 6
 
STATICTEXT #MenuList.SText, "Add a list to a menu. Choose File/Open to
add files to the list.", 10, 100, 200, 80
 
open "Adding a MRU list to a menu..." for window as #MenuList
print #MenuList, "!TRAPCLOSE [Menu.List.Exit]"
 
' ***** These statements are required to Initialise [MenuMRU] ********
'
open "user.dll" for dll as #user ' must be OPENed before first Call
MenuMRU.hWnd = hwnd(#MenuList) ' also use GetParent if Graphics window
MenuMRU.Filename$ = "" ' "" to initialise the function
MenuMRU.MaxLen = 20 ' Maximum Filename length in the Menu
MenuMRU.MenuItem = 0 ' First menu Item on the MenuBar
MenuMRU.FirstSub = 3 ' 4th Submenu item = 3
MenuMRU.LastSub = 6 ' 7th SubMenu Item = 6
gosub [MenuMRU]
'
' ***** End [MenuMRU initialisation **********************
 
[Menu.List.Loop]
Input a$
Goto [Menu.List.Loop]
 
'***************** Open a file and add it to the MRU list
[open]
filedialog "Choose file", "*.*", MenuMRU.Filename$
if MenuMRU.Filename$="" then goto [Menu.List.Loop]
[Process.File]
gosub [MenuMRU] ' ***** Add Filename to MRU list
'
' Your file processing goes here!
'
notice "File chosen is ";MenuMRU.Filename$
Goto [Menu.List.Loop]
 
'
' When the user Selects a file from the MRU list -
' retrieve it from the MenuMRU.File$ array.
'
[menu.one]
MenuMRU.Filename$ = MenuMRU.Files$(1)
Goto [Process.File] ' Goto UPDATE of the MRU list - then user processing
 
[menu.two]
MenuMRU.Filename$ = MenuMRU.Files$(2)
Goto [Process.File]
 
[menu.three]
MenuMRU.Filename$ = MenuMRU.Files$(3)
Goto [Process.File]
 
[menu.four]
MenuMRU.Filename$ = MenuMRU.Files$(4)
Goto [Process.File]
 
[Menu.List.Exit]
close #MenuList
close #user
END
 
' *************** Reusable subroutine: MenuMRU starts here ***********
'
' Subroutines:
' [MenuMRU] - Maintain a Menu of Most REcently Used (MRU) filenames.
'
' INPUT:
' MenuMRU.hWnd = hwnd of the Window with the Menu ( use
' GetParent if its a GRAPHICS Window)
' MenuMRU.Filename$ - Set to "" on the first call to initialise
' the MRU array - subsequent calls have the
' Filename to be maintained in the MRU array
' MenuMRU.MaxLen - Maximum Filename length to be used in the
' Menu display. (Filename will be abbreviated).
' MenuMRU.MenuItem - The Position of the MenuItem on the MenuBar.
' Note - the first item is 0, the second=1, etc
' MenuMRU.FirstSub - The position of the first MRU SubMenuItem in
' the list.
' Note - the first item is 0, the second=1, etc
' MenuMRU.LastSub - Position of the last MRU SubMenuItem in the list.
' Note - the first item is 0, the second=1, etc
'
' OUTPUT:
' MenuMRU.Files$( - An array of the MRUs (Most Recently Used) filenames.
'
' ASSUMPTIONS:
' 1) "user.dll" has been OPENned as #user
' 2) #user will be CLOSEd by the program
'
[MenuMRU]
' First time - Dim the arrays and Delete the Menu Items
if MenuMRU.dimmed = 0 then
MenuMRU.dimmed = 1
menuMRU.numMa = 0 ' Number of Menu slots used
menuMRU.numM = MenuMRU.LastSub - MenuMRU.FirstSub + 1
dim MenuMRU.Files$(10)
dim menuMRU.subM(10, 2) ' subM(i,1) = Item.id, subM(i,2) = Inserted
Flag
redim MenuMRU.Files$(menuMRU.numM)
redim menuMRU.subM(menuMRU.numM, 2)
gosub [menuMRU.delete]
if MenuMRU.MaxLen < 20 then MenuMRU.MaxLen = 20 'Minimum Filename
size
menuMRU.ddir$ = DefaultDir$ + "\"
end if
if MenuMRU.Filename$ = "" then return
 
menuMRU.found = 0 ' Test if file is in list
for menuMRU.i = 1 to menuMRU.numMa
if MenuMRU.Filename$ = MenuMRU.Files$(menuMRU.i) then
menuMRU.found = menuMRU.i
menuMRU.i = menuMRU.numMa
end if
next menuMRU.i
 
if menuMRU.found = 1 then return 'Already first in list
 
if menuMRU.found <> 0 then ' Move it to Top of list
menuMRU.work$ = MenuMRU.Files$(menuMRU.found)
for menuMRU.i = menuMRU.found to 2 step -1
MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
next menuMRU.i
MenuMRU.Files$(1) = menuMRU.work$
gosub [menuMRU.updateMenu]
return
end if
 
if menuMRU.numMa < menuMRU.numM then ' Add it to MRU list
menuMRU.numMa = menuMRU.numMa + 1
for menuMRU.i = menuMRU.numMa to 2 step -1
MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
next menuMRU.i
MenuMRU.Files$(1) = MenuMRU.Filename$
gosub [menuMRU.updateMenu]
return
end if
 
' Delete oldest entry - then Add new Filename
for menuMRU.i = menuMRU.numM to 2 step -1
MenuMRU.Files$(menuMRU.i) = MenuMRU.Files$(menuMRU.i-1)
next menuMRU.i
MenuMRU.Files$(1) = MenuMRU.Filename$
gosub [menuMRU.updateMenu]
 
return
 
[menuMRU.updateMenu] ' Used internally by MenuMRU
for menuMRU.i = 1 to menuMRU.numMa
menuMRU.id = menuMRU.subM(menuMRU.i, 1)
menuMRU.file$ = MenuMRU.Files$(menuMRU.i)
if upper$(left$(menuMRU.file$, len(menuMRU.ddir$))) = _
upper$(menuMRU.ddir$) then
menuMRU.file$ = right$(menuMRU.file$, _
len(menuMRU.file$) - len(menuMRU.ddir$))
end if
if len(menuMRU.file$) > MenuMRU.MaxLen then
menuMRU.file$ = left$(menuMRU.file$, 3) + " ... " + _
right$(menuMRU.file$, MenuMRU.MaxLen - 8)
end if
if menuMRU.subM(menuMRU.i, 2) = 0 then
menuMRU.subM(menuMRU.i, 2) = 1
menuMRU.insert.Flags=_MF_STRING or _MF_BYCOMMAND
calldll #user, "InsertMenu",_
MenuMRU.FileMenu as word,_
menuMRU.id as short,_
menuMRU.insert.Flags as word,_
menuMRU.id as word,_
menuMRU.file$ as ptr,_
menuMRU.results as ushort
else
calldll #user, "ModifyMenu",_
MenuMRU.FileMenu as word,_
menuMRU.id as word,_
menuMRU.insert.Flags as word,_
menuMRU.id as word,_
menuMRU.file$ as ptr,_
menuMRU.results as ushort
end if
next menuMRU.i
return
 
[menuMRU.delete] ' Used internally by MenuMRU
'*************GetMenu gets handle of menu bar
calldll #user, "GetMenu", _
MenuMRU.hWnd as word, MenuMRU.hMenu as word
 
'*************get handle of submenus
calldll #user, "GetSubMenu", _
MenuMRU.hMenu as word, _
MenuMRU.MenuItem as short, _
MenuMRU.FileMenu as word
 
'*************gets ID number for a menu item
for menuMRU.i = MenuMRU.FirstSub to MenuMRU.LastSub
calldll #user, "GetMenuItemID", _
MenuMRU.FileMenu as word, _
menuMRU.i as short, _
menuMRU.id as word
menuMRU.subM(menuMRU.i - MenuMRU.FirstSub + 1, 1) = menuMRU.id
next menuMRU.i
 
'*************Deletes the menuItems from the subMenu
for menuMRU.i = 1 to menuMRU.numM
menuMRU.id = menuMRU.subM(menuMRU.i, 1)
calldll #user, "DeleteMenu",_
MenuMRU.FileMenu as word,_
menuMRU.id as short,_
_MF_BYCOMMAND as word,_
menuMRU.results as ushort
next menuMRU.i
return
 
' ****** End of MenuMRU Subroutine ************************************


Newsletter compiled and edited by: Brosco and Alyce.

Comments, requests or corrections: Hit 'REPLY' now!

mailto:brosc-@orac.net.au

or

mailto:awatso-@mail.wctc.net