Liberty Basic is develeopped by Carl Gundel Original Newsletter compiled by Alyce Watson Translation to HTML: Raymond Roumeas
Simulating callbacks in Liberty BASIC - by Dennis McKinney
A new tool to place an LB program in the system tray - by Dennis McKinney
"Some people say I have attitude - maybe I do...but I think you have to. You have to believe in yourself when no one else does - that makes you a winner right there."
- Venus Williams
Many thanks to Dennis for the information contained in the following articles. Although he is fairly new to Liberty BASIC, Dennis is an accomplished programmer in other languages, and he brings that expertise, along with a great deal of enthusiasm to the Liberty BASIC online community. He maintains a Liberty BASIC-dedicated website. The URL follows. He writes clearly and explains things very well, and we are lucky to have him here. Thanks Dennis!
Contact Dennis McKinney:
mailto:dlm@mciworld.com
mailto:dlm81854@accs.net
The Liberty Belle:
http://belle2553.tripod.com/
It has long been known that LB can not receive callback messages the way a windows program written in some other languages can. There is a way to work around this limitation in LB, it was originaly used by Brosco. The method he pioneered was to send a resize message to a 'callback' window then call his DLL from the LB resizehandler branch to get the message. The callback window used the 'resizehandler' command to know when to retrieve the message.
This method works very well all the way through LB version 1.42. The current alpha6 release does not respond to the wm_size message at this time.
Why would you want to use callbacks? They allow your LB program and programs written in other languages to communicate with each other. A good example of this is a system tray application. LB does not have the ability to utilize the system tray directly since it requires callbacks for the mouse messages.
To build a system tray application with LB requires a companion program written in a language that can process the callbacks from the tray and pass the information to your LB program. The companion program could be a DLL or an exe program.
The methods used apply to both types. A DLL could use the event to cause LB to call a function in the DLL to retrieve the message like Brosco did. If the companion program is an exe program it could use the methods that will be demonstrated here.
Many of us lack the knowledge to write DLL's so this demonstration will deal with a companion program written in visual basic. The VB program contains a small hidden window. This window reacts to the resize message in the same way as the LB callback window.
Here is a quick outline of one way to simulate callbacks between LB and another program.
Both programs have small hidden windows that are sent wm_size messages to cause them to react and read the callback message from the other program. The actual callback messages are placed in the title bar of the LB callback window. Each program uses the GetWindowText and SetWindowText API to do this. As soon as a 'callback message' is read, the program that reads it replaces that 'message' with an empty string to avoid unwanted duplicate actions. Then the program reading the message can take whatever action is required for that message. Both programs need to obtain each others handles before these API calls can be made. A simple way to do this is to create files at runtime that contain the handle of each program and read it into memory at the start of each program.
The companion system tray program used here is named LBtray.exe. It is available at http://belle2553.tripod.com/ and includes the code listed here plus the skeleton source code to make building your own system tray applications easy.
The example program shown here will use the system tray abilities that LBtray provides to the Liberty Basic programmer.
An explanation of the setup file requirements, LBtray commands, and callback messages is included at the end of the sample program listing.
To start with, define the major variables and arrays to use.
'Constants: True = 1 'not used yet, just here to be handy False = 0 'same thing Left = 0 'left menu index Right = 1 'right menu index '-------- 'Major variables: HndLBtray = 0 'will receive hwnd of LBtray.exe WinHnd = 0 'hwnd variable used for calls Hcb = 0 'handle of callback window Mode = 0 'message parameter for [ShowWindow] SubMenu = 0 'submenu parameter for menu API calls, left or right MenuItem = 0 'menuitem parameter for menu API calls MenuFlag = 0 'parameter for menu API calls wMsg = 0 'message parameter for SendMessage API '-------- 'Arrays Dim Info$(10,10) 'for calls to Files Dim SubMenu(2) 'handles for two submenus. Dim MenuItem(2,11) 'handles for menu items in two submenus 'dimension the 2nd index to accomodate the 'larger of the two menus Dim Ttip$(3) 'array for tooltips (not required) '-------- CurIcon = 0 'current icon index (not required) CurTT = 0 'current tooltip index (not required) Ttip$(0) = "A Message From Liberty Basic" Ttip$(1) = "Second LB Tool Tip" Ttip$(2) = "Third LB Tool Tip"
The first window we need in our tray program is the callback window. This has to be a window of type 'window'. Make it small to avoid the 'flash' it would make on the screen before it is hidden.
nomainwin WindowWidth = 1 WindowHeight = 1 UpperLeftX = 1 UpperLeftY = 1 Open "Callback" for Window as #CB Hcb = hwnd(#CB) 'store permanently
As soon as it is created it should be hidden before any other windows are created. Then set the resizehandler event to this window.
Open "user.dll" for dll as #user 'leave open till the program ends. Mode = _SW_HIDE WinHnd = hwnd(#CB) Gosub [ShowWindow] Print #CB, "resizehandler [HandleResize]"
Now create the main window(s) of the program. Since this is a tray program, hide it also. I use a graphics window here just to demonstrate the way to refer to a graphics window or text window handle.
WindowWidth = 300 WindowHeight = 300 UpperLeftX = int((DisplayWidth-WindowWidth)/2) UpperLeftY = 100 Textbox #Test1.tb, 10, 50, 100, 25 Open "A Liberty Basic System Tray Program" for Graphics_nsb as #Test1 Print #Test1, "Trapclose [ReturnToTray1]" Print #Test1, "fill lightgray" Print #Test1, "flush" WinHnd = hwnd(#Test1) '------------------- 'Use GetParent for graphic or Text windows only. CallDll #user, "GetParent", _ WinHnd AS word, _ ParHnd AS word WinHnd = ParHnd '------------------- Gosub [ShowWindow]
Notice the trapclose event handler. [ReturnToTray1] will not close the application, it will only hide it and show the tray icon again. The means to exit will be provided from the system tray.
The initial communication between our applcation and the VB program is done with an initialization file. In this file specify the handle of the callback window and other info for the mouse menus and icons to use. When the VB program starts it reads this file and uses the info for it's own setup.
'create the initialization file LBtray.ini Gosub [InitLBtray] 'start the VB program so it can read the LBtray.ini file and display 'the icon in the system tray Run "LBtray.exe"
Read the file that is created by LBtray.exe when it starts that contains the handle of LBtray.exe.
'get the handle for LBtray.exe Gosub [GetMsg]
Once the handle to LBtray is obtained from the file it can be used for all further calls to send messages or to obtain information about LBtray.
Now fill the MenuItem array with the handles of the menu items for the two menus that LBtray can display.
Gosub [InitMenuArray] [Main.Loop] Input A$ Goto [Main.Loop] [Exit] Gosub [CloseLBtray] Close #user Kill "LBtray.ini" Close #CB Close #Test1 End
The rest of the program consists of the sub routines to initialize LBtray, send commands, receive and process callback messages.
'___________________________________________________________________________ [ShowWindow] 'Show or hide a window CallDll #user, "ShowWindow", WinHnd as word, Mode as ushort, result As word Return '___________________________________________________________________________ [InitLBtray] 'This is where all initial info is sent to LBTray. 'Printing to the file must follow this sequence. ' hwnd of the callback window , icons, tooltip message, left menu, right menu. Open "LBtray.ini" for output as #1 Print #1, Hcb 'handle of the LB Callback window. Print #1, "Icons" 'identifier. Print #1, 12 'number of icons to use. Print #1, 200 'initial animation frame rate. Print #1, "test.ico" 'index 0 The icon files to use. Print #1, "moon1.ico" ' 1 Print #1, "moon2.ico" ' 2 Print #1, "moon3.ico" ' 3 Print #1, "moon4.ico" ' 4 Print #1, "moon5.ico" ' 5 Print #1, "moon5.ico" ' 6 Print #1, "moon7.ico" ' 7 Print #1, "moon8.ico" ' 8 Print #1, "book1.ico" ' 9 Print #1, "book2.ico" ' 10 Print #1, "book3.ico" ' 11 Print #1, "A Message From Liberty Basic" 'Default Tooltip message. Print #1, "Lmenu" 'Left menu identifier. Print #1, 3 'number of items in left menu. Print #1, "About" ' L0 (returned value). The number is the menu index number. Print #1, "-" ' index will not be returned for separator bars Print #1, "Exit" ' L2 Print #1, "Rmenu" 'Right menu identifier. Print #1, 11 'number of items in right menu. Print #1, "Show LB program" ' R0 (returned value). Print #1, "-" Print #1, "First Animation" ' R2 Print #1, "Second Animation" ' R3 Print #1, "Fast Animation" ' R4 Print #1, "Slow Animation" ' R5 Print #1, "Stop Animation" ' R6 Print #1, "-" Print #1, "Change Icon" ' R8 Print #1, "-" Print #1, "Change Tooltip" ' R10 Close #1 Return '___________________________________________________________________________ [CloseLBtray] wMsg = _WM_CLOSE Gosub [NotifyLBtray] Return '___________________________________________________________________________ [InitMenuArray] 'This routine will fill the MenuItem array with the menuitem handles of both 'menus regardless of how many items are used. It also fills the SubMenu array 'with the handles of LBtray's sub menus. CallDll #user, "GetMenu", HndLBtray as word, HMnuBar as word 'LBtray allows two submenus. Their indexes are '0 for the left menu and 1 for the right menu. For MPos = 0 to 1 CallDll #user, "GetSubMenu", HMnuBar as short, MPos as short, HndSubMnu as short SubMenu(MPos) = HndSubMnu CallDll #user, "GetMenuItemCount", HndSubMnu as short, MiCnt as word For IC = 0 to (MiCnt - 1) CallDll #user, "GetMenuItemID", HndSubMnu as word, IC as short, ID as word MenuItem(MPos,IC) = ID Next IC Next MPos Return '___________________________________________________________________________ [EnableMenuItem] 'enable, disable, or gray/disable a menu item 'This routine is only one of the many menu manipulations available via API. 'It's probably the one you will use the most. See [PopUp.1] and [ReturnToTray1]. 'get the handle of the correct submenu HndSubMnu = SubMenu(SubMenu) 'get the handle of the correct menu item MenuID = MenuItem(SubMenu,MenuItem) CallDll #user, "EnableMenuItem", _ HndSubMnu as word, MenuID as word, MenuFlag as word, Result as word Return '___________________________________________________________________________ [HandleResize] 'this routine executes when the LB callback window gets a WM_SIZE message 'get the message from the callback window title bar Gosub [GetMsg] If Msg$ <> "" Then 'Clear the title bar Clr$ = "" CallDll #user, "SetWindowText", Hcb as word, Clr$ as ptr, Result as void Gosub [ProcessMsg] End If If Msg$ = "" Then 'Handle any resize events not caused by a callback here. Such as a user 'resizing the window. End If Goto [Main.Loop] '___________________________________________________________________________ [GetMsg] 'when the callback window is first hidden it will trigger the resize handler. 'Since LBtray will not be created at that time don't try to process messages 'until the handle to LBtray has been retrieved. If HndLBtray <> 0 Then Msg$=" " + Chr$(0) Length = Len(Msg$) CallDll #user, "GetWindowText", Hcb as word, Msg$ as ptr, _ Length as ushort, Result as ushort Msg$=Left$(Msg$,Result) wMsg = _WM_SIZE end if 'get the handle for LBtray if we don't have it yet If HndLBtray = 0 Then Files DefaultDir$, "Hndl.inp", Info$( If val(Info$(0, 0)) > 0 then 'get the handle for LBtray. Open "Hndl.inp" for input as #1 Input #1, HndLBtray$ HndLBtray = val(HndLBtray$) Close #1 End If End If Return '___________________________________________________________________________ [ProcessMsg] 'handle the callback message from LBtray 'when a tray menu item is clicked LBtray will place a string (text) in 'the title bar of the LB callback window. The string will start with 'either 'L' for the left menu or 'R' for the right menu. The letter 'will be followed by a number representing the index number of the 'menu item that was clicked. The range of numbers is determined by 'the number of menu items you specify in [InitLBtray]. The only other 'message returned might be 'LDCLICK'. If Msg$ = "L0" Then Gosub [About] If Msg$ = "L2" Then Gosub [Exit] If Msg$ = "R0" Then Gosub [PopUp.1] If Msg$ = "R2" Then First = 9 'index of first icon to animate Last = 11 'index of last icon to animate Gosub [Animate] End If If Msg$ = "R3" Then First = 1 Last = 8 Gosub [Animate] End If If Msg$ = "R4" Then Gosub [Fast] If Msg$ = "R5" Then Gosub [Slow] If Msg$ = "R6" Then Gosub [Stop] If Msg$ = "R8" Then Gosub [ChangeIcon] If Msg$ = "R10" Then Gosub [ChangeTT] If Msg$ = "LDCLICK" Then 'Only returned if there is no left menu. Gosub [Exit] End If Return '___________________________________________________________________________ [SendUserMsg] 'send a valid message to LBtray calldll #user, "SetWindowText", Hcb as word, NewMsg$ as ptr, Result as void Gosub [NotifyLBtray] Return '___________________________________________________________________________ [NotifyLBtray] 'let LBtray know a message has been sent. The message is _WM_SIZE, 'contained in wMsg. The only other message sent is _WM_CLOSE, sent at the 'ending of the LB program. CallDll #user, "SendMessage", HndLBtray as word, wMsg as word, _ wParam as word, lParam as long, Result as long Return '___________________________________________________________________________ [PopUp.1] 'show an LB window Mode = _SW_SHOW Gosub [ShowWindow] Print #Test1.tb, "!setfocus" NewMsg$ = "HideIcon" Gosub [SendUserMsg] 'If you just want to disable this menu item while your LB window is visible then 'use the next four lines instead of the last 2 lines above. Make the same changes 'in the branch [ReturnToTray1]. ' SubMenu = Right 'R0 was returned from LBtray ' MenuItem = 0 ' MenuFlag = _MF_GRAYED ' Gosub [EnableMenuItem] Return '___________________________________________________________________________ [ReturnToTray1] Mode = _SW_HIDE Gosub [ShowWindow] NewMsg$ = "Restore 0" Gosub [SendUserMsg] ' SubMenu = Right ' MenuItem = 0 ' MenuFlag = _MF_ENABLED ' Gosub [EnableMenuItem] Goto [Main.Loop] '___________________________________________________________________________ [About] Text$ = "LBtray is a Liberty Basic companion that provides" + chr$(13) + _ "full runtime system tray features for" + chr$(13) + _ "Liberty Basic programs" Notice "About LBtray" + chr$(13) + Text$ Return '___________________________________________________________________________ [Animate] NewMsg$ = "Animate" + " " + str$(First) + " " + str$(Last) Gosub [SendUserMsg] Return '___________________________________________________________________________ [Fast] NewMsg$ = "SeqTime 200" Gosub [SendUserMsg] Return '___________________________________________________________________________ [Slow] NewMsg$ = "SeqTime 1000" Gosub [SendUserMsg] Return '___________________________________________________________________________ [Stop] NewMsg$ = "EndAni" Gosub [SendUserMsg] Return '___________________________________________________________________________ [ChangeIcon] CurIcon = CurIcon + 1 If CurIcon > 11 then CurIcon = 0 NewMsg$ = "ShowIcon" + " " + str$(CurIcon) Gosub [SendUserMsg] Return '___________________________________________________________________________ [ChangeTT] CurTT = CurTT + 1 If CurTT > 2 Then CurTT = 0 tt$ = Ttip$(CurTT) NewMsg$ = "CttMsg" + " " + tt$ Gosub [SendUserMsg] Return '___________________________________________________________________________
End of program listing
Details of initialization file settings, runtime commands for the LB program to send to LBtray, and callback messages from LBtray.
Commands are not case sensitive.
================================================================= Info to be printed to "LBtray.ini" must be in the order shown. Required Type Info ----------------------------------------------------------------- Yes Number Handle of the window used to send and recieve messages. Yes String "icons" (literal string) Yes Number Number of icons to load. Minimum is 1. Yes Number Animation sequence time (1000 = 1 second). Minimum is 100. Maximum is 65,535. Yes String Icon name (include path if in sub dir, ie DefaultDir$ + "\ico\myicon.ico"). Use one entry for each icon name. Icons listed here will be assigned an index number beginning with 0. Bitmaps may not be used. Yes String Tooltip message for the tray icon. Maximum length of the message is 64 characters. Yes String "Lmenu" (literal string) Yes Number Number of items in left mouse button menu including separator bars. Use 0 if no left menu is desired. NOTE: If 0 is specified then double clicking on the the tray icon with the left mouse button will cause a message of "LDCLICK" to be sent to Hcb. See above String Menu item. Can not include ampersand. Use one entry for each menu item. Separator bars are entered as "-" Yes String "Rmenu" (literal string) Yes Number Number of items in right mouse button menu including separator bars. Use 0 if no right menu is desired. No String Menu item. Can not include ampersand. Use one entry for each menu item. Separator bars are entered as "-"
When a menu item is clicked, a letter and number will be printed to the Hcb title bar. The letter will be L for left or R for right.
The letter will be followed by the index number of the menu item. ie "L2" or "R0" etc. Any separator bars you specify will increase the index number of the next menu item by 1.
Index numbers start at 0, not 1. If you list your menu items as
"Exit" "-" "Menu 1" "-" "Menu 2" "Menu 3"
then the index number for "Exit" is 0, "Menu 1" is 2, "Menu 2" is 4, "Menu 3" is 5. Index numbers for "-" will not be returned.
Here's an example of an LBtray.ini setup.
Hcb = hwnd(#CallbackWindow) Open "Lbtray.ini" for output as #LBini print #LBini, Hcb 'Handle of the window to send and recieve messages. print #LBini, "icons" print #LBini, 3 'Number of icons you are using. print #LBini, 200 'Animation sequence time. Experiment. print #LBini, "test1.ico" 'This icon will have an index of 0. print #LBini, "test2.ico" 'This icon will have an index of 1. print #LBini, "test3.ico" 'This icon will have an index of 2. print #LBini, "A Message From Liberty Basic" 'The tooltip message print #LBini, "lmenu" print #LBini, 3 'The number of items in the left menu. print #LBini, "LB Left Menu 1" 'This will have an index of 0. print #LBini, "-" 'Separator bar print #LBini, "LB Left Menu 2" 'This will have an index of 3. print #LBini, "rmenu" print #LBini, 1 print #LBini, "LB Right Menu 1" 'This will have an index of 0. Close #LBini
When your program starts LBtray the first icon listed will be displayed in the system tray.
================================================================== COMMANDS LBTray will process these commands. The entire command is a string (text). Command Info ----------------------------------------------------------------- "HideIcon" Removes the icon from the system tray. Use this command when your program is no longer minimized to the system tray, but might be minimized again. "Restore X" Restores the system tray icon. Use after using the 'HideIcon' command. X = index of the icon to display. Must be an icon listed in LBTray.ini. If animation was in progress at the time 'HideIcon' was used, that animation sequence will continue after the single icon has been displayed. You may want to keep track of any animation sequence in effect and specify the first icon of that sequence for the X parameter. "ShowIcon X" Where X = index of the icon. Must be an icon listed in LBTray.ini. Displays a single icon in the system tray. Will stop any animation. "Animate A B" Starts or changes an animation sequence. A = the starting animation icon. B = the ending animation icon. Any range of animation icons may be specified as long as they have been listed in LBTray.ini. "Animate 3 5" will cycle the animation icons whose index is 3, 4, and 5 in a loop. Make sure the number specified first is smaller than the second number. "EndAni" Stops animation. Should be followed by "ShowIcon X" to display the icon you want. "CttMsg X" Changes the Tooltip Message. X = the new message. 64 characters maximum length. "SeqTime X" Changes the animation sequence timing. X = time. 1000 is about 1 second. The maximum is 65,535 or just over 1 minute. =================================================================
LBTray will return these messages to the Hcb window.
Message Info ----------------------------------------------------------------- "LX" L means the left menu. X is the index number of the menu item. "RX" R means the right menu. X is the index number of the menu item. "LDCLICK" This will be returned only if no left menu exists and the user double clicks the icon with the left mouse button.
Comments, requests or corrections: Hit 'REPLY' now! Dean Hodgson -- mailto:Hodgson.Dean@saugov.sa.gov.au Alyce Watson -- mailto:awatson@wctc.net