Older Version Newer Version

DuncanB DuncanB Jun 17, 2007 - "First Draft DuncanB"

Running a LB program from the System Tray.

Author : Duncan Bushaw

Published June 17, 2007.



Introduction.


The example program 'TrayIcon.bas' (hereafter refered to as the program) uses Brent Thorns' WMLiberty.dll which is included in this file . The source code for Brent Thorns' WMLiberty.dll is available @ Bay 6 Software @ http://www.b6sw.com/. A full working package of the program can be downloaded from my domain @ http://duncanb.nh-networks.com/.

For the purpose of this article a windowless control is any type of control that does not necessarily have to be assigned to a active window (ie; Liberty BASIC controls such as NOTICE or POPUPMENU; windows API controls such as ShellAboutA or MessageBoxExA), and a Window is any window created using the Liberty BASIC 'OPEN' command.

Any standard approach to writting Liberty BASIC code only seems to work for very basic programs that have just a popup menu. At some point in writting more complex code the program starts to get various error while testing it.

The most common errors I see and hear about are the 'Invalid branch label', 'stack overflow', and 'popup menu already active' errors.

The errors have to do with the CALLBACK function TrayIcon() [This is the CALLBACK function that is tripped by the CALLDLL command which uses the WMLiberty.dll that I used in the program] which is called whenever the mouse (cursor) moves over the icon on the system tray this causes the program to look only at the code in the program that is contained with in the TrayIcon() CALLBACK funtion. I used the GLOBAL variable (TrayIconPointer) to remind the program what it should be doing whenever the TrayIcon() CALLBACK function is tripped.

The program uses many Liberty BASIC commands and a few API controls, and shows one technique that seems to encompass most controls and windows with 2 Liberty BASIC windows opened at one time and enabled without any of the previously mentioned errors occurring.

The technique used in this program is not the only way of writing a program in Liberty BASIC which can run on the system tray but I find it I useful technique that seems to encompass all possible controls whether Liberty BASIC native commands or windows API calls .

To do this I had to 'Think Outside Of The Box' when using function calls I created in the program and one will see a lot of WAIT commands, which in itself is not unexpected, but the difference is that when the TrayIcon() CALLBACK function is called and a window is then opened using it the program does not exit the function which is called to open the window whereas when the function which opens the window is called directly by the program and not through the TrayIcon() CALLBACK function in must be exited properly for the program to continue working correctly.

Think of it this way, only when a windowless control has been activated will the program exit the TrayIcon() CALLBACK function and if any window or no windowless control has been activated the program will encounter the Liberty BASIC 'WAIT' command and will then only refer to the code in TrayIcon() CALLBACK function, if no windows are active.

If the 'Test Window' is active the program will call the Testwindow() function and then only refer to the code in this function.

The same idea happens with the dialog window active but it will call the Retrieve() function which contains duplicated code from the TestWindow() function so both windows can be enabed and all controls working.

When the program closes the 'Test window' and the TrayIcon() CALLBACK function is active the program must encounter a 'WAIT' command and not exit the Testwindow() function, if the TrayIcon() CALLBACK function is not active the program must exit the Testwindow() function.

To show this the Testwindow() function can be called two ways before or after the TrayIcon() CALLBACK command is initiated.

IF INSTR(CommandLine$,"-TestWindow")>0 then OP=1:null=TestWindow()

The above line of code is why the TKN file and the runtime engine must be in the same folder/directory as the BAS file as it is how the menuitem 'Test window on Restart' opens the window before putting the icon on the system tray.

If the CommandLine$ contains the string "-TestWindow" in it then program sets the GLOBAL variable (OP) to the value of 1 and then calls the TestWindow() function which will then open the Test Window and the program will wait and update the clock in the GRAPHICBOX control every second using the TIMER command.

All branch labels for the window are contained within the function code itself and when the close button on the title bar is clicked or (ALT-F4) is pressed the program looks for the branch lable [quit.WindowIcon] within the TestWindow() function and turns off the TIMER, closes the window, sets the TrayIconPointer to the value of Zero, and then checks the (OP) variable. If it is 0 then the program does not exits the function but waits and must do so when called from the TrayIcon() CALLBACK function to prevent errors from happening. However if the dialog window has also been opened then the branch label [quit.WindowIcon] is looked for in the Retrieve() function as that is the active function at that time.

TrayIcon.bas the program.


The Liberty BASIC runtime engine (run403.exe) renamed as TrayIcon.exe and the TKN (tokenized BAS file) must be in the same dir./folder as TrayIcon.bas for all the program features to work without errors. A full working package can be downloaded from my domain @ http://duncanb.nh-networks.com/.

The program starts up by extracting the icon from TrayIcon.exe and puts this icon on the system tray, from here there are 2 popup menus available.

Double clicking the left mouse button calls 1 and with a single click of the right mouse button a popup menu will appear when the button is released. It is best not to make a popup menu appear when a mouse button is pressed down but when the mouse button is released, this allows any other popup menus that maybe active to close before the new popup menu is displayed.

The left button menu has 4 menutiems which are 'About', 'Test window', 'NOTICE', and 'Help'.

The right button menu has 4 menutiems which are 'Test window', 'Test window on Restart', 'About', and 'Exit'.

The 'About' menuitems displays the systems about dialog window.

The 'NOTICE' menuitem uses the native LB 'NOTICE' command.

The 'Help' menuitem opens the systems default browser and goes to this LBPE web site.

The 'Exit' menuitem ends the program.

The 'Test window on Restart' starts another instance of the program, which will show the Test window before putting the icon on the system tray, and then ends the first instance of the program. YOU must have a compiled (TKN) file in the same directory as TrayIcon.bas, and have a copy of the runtime engine (run403.exe) named TrayIcon.exe for this to work.

The 'Test window' will open the Test window which is of a window_nf type, and the tray icon will have a different popup menu available with the right mouse button. The left mouse button is disabled while the Test window is open.


The Test window.


Is of a window_nf type.

I have included many native Liberty BASIC controls in the Test Window, they are BUTTON, CHECKBOX, COMBOBOX, GRAPHICBOX, GROUPBOX, LISTBOX, MENU, NOTICE, POPUPMENU, RADIOBUTTON, STATICTEXT, TEXTBOX, TEXTEDITOR, and TIMER.

Selecting a CHECKBOX, COMBOBOX, LISTBOX, or RADIOBUTTON item a NOTICE window will appear displaying the selected item or state of control (set/reset).

The GRAPHICBOX is used to display a clock which I ported from 'clasic Face clock.bas' (see this article of mine http://lbpe.wikispaces.com/A+True+API+Popup+Menu+-+By+Duncan+B) which uses the TIMER control to update the clock every second.

The 'Retrieve Text' button will retrieve anything in the TEXTBOX and display it in a NOTICE window.

The 'Retrieve All Items' button will cause a POPUPMENU to appear and the user can select which window the dialog_nf_modal window will be attached to (the hidden window or the Test Window). After selecting the window to attach the dialog_nf_modal window to the program will then open the dlalog_nf_modal window with only a TEXTEDITOR control in the window (neat no menu bar in this window) and then retrieve the current state of all CHECKBOXES and RADIOBUTTONS (set/reset), the current selected LISTBOX and COMBOBOX items (if any), the contents of the COMBOBOX, TEXTBOX, and TEXTEDITOR (if any), and then display them in the TEXTEDITOR in the dialog_nf_modal window.

The 'Retrieved All Items' window.


Is of a dialog_nf_modal type.

When this window is opened the tray icon will have a different popupmenu again (right click only) and the clock in the GRAPHICBOX in the 'Test Window' will continue to be updated every second.

If this window is attached to the 'Test Window' then the 'Test Window' will be disabled but will not look like it is as the program makes the 'Test Window' the foreground window even though the dialog_nf_modal window is on top. Clicking on the 'Test Window' with the mouse will cause it to gray and make the dialog_nf_modal window the foreground window.

If this window is attached to the hidden window then the 'Test Window' will not be disabled and will be set as the foreground window putting it on top of the dialog_nf_modal window. Moving the mouse over (do not click) the tray icon the 'Retrieved All Items' window will be set as the foreground window putting it on top of the 'Test Window'. I did this to show that the CALLBACK function will be tripped just by moving the mouse over the tray icon. The 'Test Window' will remain enabled and all the controls still work.


The TrayIconpointer.


This variable works best if it is set before any windows or windowless controls have been opened or are made active .

The TrayIconpointer variable has 4 states:
  • [-1] - This value tells the program that a windowless contol is active and to exit the Traylcon() CALLBACK function right away.
  • [0] - This value tells the program that there are no windowless controls or windows active and causes the program to wait inside the Traylcon() CALLBACK function.
  • [1] - This value tells the program that the Test window is active and causes the program to jump to the Testwindow() function and to wait inside the function.
  • [2] - This value tells the program that the dialog window is active and causes the program to jump to the Retrieve() function and to wait inside the function.

I hope that this helps those frustrated LB programmers who have tinkered with the WMLiberty.dll, I spent many hours trying different codes and this solution seems so obvious to me now. Enjoy!