I have always thought that Liberty Basic, although quite powerful, was missing a few key commands and/or events. One of the events that I wished was present, and whose absence has troubled me more than once - is the "Lost Focus" event. The purpose of the lost focus event is to trigger a call to a specific label from which to execute code, whenever a specified window or control either gains focus or loses focus.
If you have played around with Visual Basic before, you may recognize this
event. It is sometimes used in that environment to detect when a textbox that
had focus (and presumably some sort of input) has subsequently lost
focus. This is useful to perform validation of the text entered into the field. Likewise, determining when a form (or window) has lost focus could prove useful for many reasons. For instance it could trigger a repaint event, or possibly initiate a routine that could automatically store data, which a user has been working on should the program lose focus.
The possibilities abound.
But the lack of the "Lost Focus" event in Liberty Basic is not the liability it would seem, as the language allows us to improvise easily. Access to the windows API is one of our greatest assets when "impossible" situations arise that demand a response. So, having asked for this functionality several times (and Carl has given it consideration) - I decided that maybe there was already a method that I could employ to achieve the results I was seeking.
I turned to Dave Appleman's book titled Win32 API for Visual Basic. I like
this book a lot, because the examples are clear and easy to read for someone
accustomed to Basic. After a bit of searching I ran across the very API call
I was hoping to find. It was the simple little "GetFocus" call, which is part of the User32.DLL. The Liberty Basic syntax to call this API looks like this:
CallDLL #user32, "GetFocus", handle As long
Very simple. Issue the command and in a split second you are returned the
handle of the window that has the focus. In fact if you were to use the Liberty
Basic command hWnd(#main) and compare the value it returns with the value
returned from the "GetFocus" API call (presuming that the main window has focus) then you would observe that the two values would be identical!
But how could I turn this one simple command into a tool that I could use to determine what window had focus and take appropriate action depending on the results?
The answer lay in the fairly new Liberty Basic command: TIMER. (Timer was added to the Liberty Basic language with the version 2 release.) The HELP file says the following about the timer command:
TIMER milliseconds, [branchLabel]
Description: This commands manages a Windows timer. This is useful for controlling
the rate of software execution (games or animation perhaps), or for creating
a program or program feature which activates periodically (a clock perhaps,
or an email client which checks for new messages). Deactivate the timer with
a time value of 0, and
no branch label. There is only one timer. The elapsed time value and/or branch label to execute can be changed at any time by issuing a new TIMER command. There are 1000 milliseconds in one second. A value of 1000 causes the timer to fire every one second. A value of 500 causes the timer to fire every half second, and so on.
'set a timer to fire in 3 seconds timer 3000, [itHappened] 'wait here wait
[itHappened] 'deactivate the timer timer 0 confirm "It happened! Do it again?"; answer if answer then 'reactivate the timer timer 3000, [itHappened] wait end if end
To make the timer command work for us in determining who has focus is a matter of following a few simple steps.
1) Determine what handle you are trapping for (say the handle of the main
window) and save that value.
2) Initiate a timer and point it at an event handler.
3) In the event handler compare the value of the window that currently has focus to the value saved in step 1
4) If the value has changed - take action.
If the timer were set to 50 or 100 milliseconds, each interval the system would check to see "who" had focus and compare it with "who" we thought should have focus. In step four, should the values be different, we will have triggered a "Lost Focus" event and we can take appropriate action.
Having established the process, a word about windows and controls would be
in order before we continue. In WINDOWS (the operating system) nomenclature
a window is the form on which controls are placed, but it is also each and
every control as well. In fact they are a lot like child windows of the form
(e.g. window) on which they are placed.
The API call "GetFocus" returns the value of both windows and controls (which are placed on the windows - since, as I explained, they are really windows as well).
This means that your main window may not have actually lost focus even though the value returned by the API function "GetFocus" has changed. This is because the user may have changed the focus to one of the controls on the window. So when writing code to test for lost focus of the form (e.g. window), we must account for all the controls that are on the form as well, since they too have a valid reason to receive focus.
The second implication of this fact is that you can also trap for the event when focus moves from one control to another control. Do this by obtaining the handle value for a control on the form, such as a textbox, for instance using the following command:
handle = hWnd(#main.textbox1)
Once you have that value you would be able to trap the moment when the focus was both gained and lost for that control by following steps two through four. The uses of such event traps are many - only your imagination is the limit.
So, let me put it all together in a little demonstration program. This program, called HocusFocus.bas is attached as a zip file, as well as being annotated here in this article.
First the program header information. If you write programs which you share, please allow me to take just a second and encourage you to always include a few lines of comment about your code. It should state the name of your program, what your program does, who wrote the program and for which version of what language you have written it. Also include a version number or date of last revision (this helps the rest of us out in case more than one version of the code is floating around). Don't forget to mention the copyright status and what criteria need to be met to use your work. For this simple demo the following is included:
' HocusFocus.bas ' by Brad Moore - copyright 2002 ' Last revision 6/17/2002 '----------------------------------------------- ' Inclusion within other works is permitted ' without requirement of credit or permission. ' All rights are retained by author. '----------------------------------------------- ' This is written for Liberty Basic v3.x ' To learn more about Liberty Basic please ' visit the website http://www.libertybasic.com
With this out of the way, I begin by following standard practices of setting up a simple window with a couple controls on it. If you need help understanding this section please refer to the Liberty Basic Help System.
'Initalize the settings for the window ForegroundColor$ = "Black" BackgroundColor$ = "Buttonface" TexteditorColor$ = "White" TextboxColor$ = "White" NoMainWin WindowWidth = 393 : WindowHeight = 210 UpperLeftX = Int((DisplayWidth-WindowWidth)/2) UpperLeftY = Int((DisplayHeight-WindowHeight)/2)
'setup the controls Statictext #main.st1, "", 80, 20, 130, 25 Statictext #main.st2, "", 55, 54, 155, 25 Statictext #main.st3, "", 20, 88, 195, 25 Button #main.quit, "",[quit],UL, 220, 125, 140, 35 Textbox #main.tbx1, 220, 15, 137, 24 Textbox #main.tbx2, 220, 50, 137, 24 Textbox #main.tbx3, 220, 85, 137, 24
Open "Hocus Focus" For Window As #main
#main "trapclose [quit]" #main "font ms_sans_serif 10"
I have chosen to write the static text out to the window after the window is created. You don't have to do this, I do it to keep my code line short and readable without line wraps.
'I assigned text to labels to avoid line wrap above #main.st1 "Handle of this window" #main.st2 "Last window to have focus" #main.st3 "What window/control has focus?" #main.quit "I am done"
Now I get the handles of all the controls on the window and the handle of the window, too. I will use these later to determine where the focus is during program execution.
'Enumerate the open windows/controls in the lb form hmain = hWnd(#main) tb1main = hWnd(#main.tbx1) tb2main = hWnd(#main.tbx2) tb3main = hWnd(#main.tbx3) btnmain = hWnd(#main.quit)
Now, knowing these values, I plug a couple of the fields with the handle values.
'Intialize values for the textboxes #main.tbx1 Str$(hmain) #main.tbx2 Str$(hmain) #main.tbx3 "Main Window"
Now I initiate the time control and point it to my event handler each iteration
the timer fires. Timers can be confusing - the thing to remember is that they
are simply a control mechanism that causes the program pointer (an imaginary
that keeps track of what line of code to execute next) to be redirected to a specific label to begin executing code, and it causes this redirection on a regular basis which is determined by the time interval you specify. A second important thing to remember about timers is that although they are driven by the PC's system clock, they are not precise to the millisecond. This means that just because you specify 100 milliseconds, you may not realize the exact resolution in timing. It will not run faster, but may run slower. This is because the timer is limited by the other functions that Windows is performing elsewhere. Odds are good Liberty Basic will not receive Windows attention at the exact moment that your timer should fire, resulting in a latency effect. You timer will fire every 110 milliseconds instead of every 100 milliseconds. That is the way windows is. Enough of the technical stuff - if the concept of timers is still a bit unclear please experiment with some of the examples in the help file. Information on the timer command is available in several places there. In the case of the code below, I am simply causing program execution to go to the label [events] every 50 milliseconds.
'set the timer to 50 milliseconds Timer 50, [events]
Finally we wait.
'Wait for events Wait
There are only two events which are handled in the remaining code. The first simply handles the close event, closing the main window and halting program execution.
'Process the events [quit] Close #main End
The last event is the one that does the work of the demo. The first thing that is done each time this code is executed is the dll call to the "GetFocus" function which returns the handle of the window or control that has focus. That handle value is updated in the textbox #main.tbx2.
[events] 'Find out who has the focus currently CallDLL #user32, "GetFocus", handle As long 'Display the value #main.tbx2 Str$(handle)
Next the value returned is evaluated in a select - case statement against each of the known good handles for the window and its controls. If the focus is found to be any of the know values then the third textbox is updated with a text string literal which tells the user what window or control has the focus. If none of the known windows or controls has focus, then we declare that focus has been lost by the application and an external window currently has the focus.
'Evaluate the result of call Select Case handle Case hmain #main.tbx3 "Main Window" Case tb1main #main.tbx3 "Textbox 1 Control" Case tb2main #main.tbx3 "Textbox 2 Control" Case tb3main #main.tbx3 "Textbox 3 Control" Case btnmain #main.tbx3 "Quit Button Control" Case Else #main.tbx3 "External Window" End Select
Finally, after all is said and done we wait until the timer fires again.
I hope it all made sense. Maybe someday you will find a need where this can be integrated into a project you are working on. Until then, thanks for staying with me.