Liberty Basic is develeopped by Carl Gundel Original Newsletter compiled by Alyce Watson and Brosco Translation to HTML: Raymond Roumeas
In this issue:
In future issues:
Add/Remove SubMenus and Menu Items.
Adding a Most Recently Used list to a menu.
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
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
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
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 ************************************
Comments, requests or corrections: Hit 'REPLY' now!
mailto:brosc-@orac.net.au
or
mailto:awatso-@mail.wctc.net