BBC BASIC for Windows
« Alternative Event library. »

Welcome Guest. Please Login or Register.
Apr 5th, 2018, 9:57pm



ATTENTION MEMBERS: Conforums will be closing it doors and discontinuing its service on April 15, 2018.
Ad-Free has been deactivated. Outstanding Ad-Free credits will be reimbursed to respective payment methods.

If you require a dump of the post on your message board, please come to the support board and request it.


Thank you Conforums members.

BBC BASIC for Windows Resources
Online BBC BASIC for Windows documentation
BBC BASIC for Windows Beginners' Tutorial
BBC BASIC Home Page
BBC BASIC on Rosetta Code
BBC BASIC discussion group
BBC BASIC for Windows Programmers' Reference

« Previous Topic | Next Topic »
Pages: 1  Notify Send Topic Print
 thread  Author  Topic: Alternative Event library.  (Read 1431 times)
Zaphod
Guest
xx Alternative Event library.
« Thread started on: Apr 11th, 2016, 4:05pm »

An alternative event handler.

Eventlib is the official event handling library but here is an alternative. First, I need to say that this draws very heavily on the code in Richard's libraries as to technique rather than the code itself, but this variation could not have been written without that inspiration.

So why even bother with an alternative library?
Eventlib serializes all events and allows you to register events to go to particular event message type handlers. So all WM_COMMAND events, button clicks and menu clicks would go to one type of handler. If you register several events such as a series of buttons then the handler procedures are added to a linked list and then when an event of type WM_COMMAND is received it is handed to each registered button handler and any other WM_COMMAND handlers in turn, starting with the last one defined.
If you had, say, 20 buttons this seems sub-optimal. You would be directed to each of the handlers in turn and that handler would have to check the ID of the event to see if it was meant to handle it. And even after the event was handled the event information would then passed to all the other button handlers in turn anyway. Now BB4W is fast so there is probably no significant speed penalty in all this extra work but it just seemed unnecessary in most cases.
And there looked to be an easier way for a control centric event handler and that is to use the mechanism employed by WINLIB5's FN_setproc.
If we then revamp that system, we can register events by the control ID as well as the message types in groups. If we put all WM_COMMAND and WM_NOTIFY into one group we can then use the control ID as the index into the event handler lookup array. All of which is probably as clear as mud to most readers!
What it means is that a control event has a procedure specific to it, not general to the event type. But since FN_setproc already has that covered what we needed was to add that kind of functionality to the event buffering and serialization that Eventlib has.
Nothing new then, just a hybridization of Eventlib and FN_setproc to make a control centric buffer, with serialization and event handler selection by ID and/or message group.
It avoids the overhead of having to check against multiple handlers as there will be a specific handler for each control as required, up to 900+ controls. It should be marginally faster, as it won't have to check lots of handlers to find the right control, which is probably good when dealing with interrupts. It certainly involves less user code and not even a CASE statement to sort out the button ID's, it hands you the right one straight off.
Now the code.

We have added a few bells and whistles so that it keeps up with the current Eventlib in flexibility and abilities. There is a sacrifice, though, if you want to find other event types that don't relate to controls, in that they are grouped and would need to be separated out by the user. But as we shall see having them grouped may not be a bad thing anyway.

Code:
      REM V1.00  9-Feb-2016    Rewritten, enlarged buffer and over 900 controls and menu inputs. Unhandles events handed to ON SYS line.

      REM Any ON SYS line must preceed any Event library command.
      REM All PROC_initxxx calls must be made after any ON SYS line.
      REM Events normally routed to ON SYS will be handled by specific handlers via the  libraries. Unhandled ON SYS events will be route to the ON SYS line if it exists.
      REM If no ON SYS line exists then any unhandled  events will be ignored. (As usual)
      REM Control IDs automatically assigned to &7FFE and decreasing.


      DEF PROC_initonmove(RETURN F%)
      LOCAL E%
      E%=FN_regevent(7,F%)   :REM All move/resize events linked to "ID" slot 7.
      !396=!400              :REM Redirect the ON MOVE to the new ON SYS queue.
      ENDPROC

      DEF PROC_initonmouse(RETURN F%)
      LOCAL E%
      E%=FN_regevent(6,F%)
      !404=!400
      ENDPROC

      DEF PROC_initontimer(RETURN F%)
      LOCAL E%
      E%=FN_regevent(5,F%)
      !388=!400
      ENDPROC

      DEF PROC_initsweeper(RETURN F%)  :REM SYS 1 less WM_Notify mesages
      REM a link to the remaining events, mainly non control related. @msg% 287,279,278,123,83,786,563
      LOCAL E%
      E%=FN_regevent(4,F%)
      ENDPROC

      DEF PROC_initkb(RETURN F%)   :REM Simulates an interrupt from key press. Scans KB buffer as part of PROC_DoEvents.
      E%=FN_regevent(3,F%)
      ENDPROC

      DEF PROC_trapcancel(RETURN F%)  :REM traps ID_CANCEL to defined procedure. Needed with WINLIB2B. If not used then Cancel routed to legacy ON SYS
      E%=FN_regevent(2,F%)
      ENDPROC

      DEF PROC_trapok(RETURN F%)  :REM traps ID_OK to defined procedure. Optional. ID of 1. If not called routes ID value of 1 to legacy ON SYS.
      E%=FN_regevent(1,F%)
      ENDPROC


      DEF FN_regproc(RETURN F%)
      REM Used within  libs to allocate control and menu IDs
      IF F%=0: =0
      REM top 7 slots reserved for the above.
      PRIVATE E% :LOCAL D%: E%+=1 D%=E%+8: IF D% = &3F0 ERROR 100, "Too many controls."
      DEF FN_regevent(D%,F%)    :REM force particular handlers that are not control ID dependent. Used to direct non ON SYS events from the common buffer to special handlers.
      LOCAL S%: S%=!400         :REM Save current ON SYS vector prior to this buffer.
      DIM F@%(&400), Z@%(99)    :REM  Define 32 event buffer and space for 1000 controls.
      F@%(D%)=F%                :REM Store the handler PROC address in an array indexed by the control ID.
      ON SYS Z@%()=Z@%(0)+(3 AND Z@%(0)<99),@msg%,@wparam%,@lparam%,Z@%(1),Z@%(2),Z@%(3),Z@%(4),Z@%(5),Z@%(6),Z@%(7),Z@%(8),Z@%(9),Z@%(10),Z@%(11),Z@%(12),Z@%(13),Z@%(14),Z@%(15),Z@%(16),Z@%(17),Z@%(18),Z@%(19),Z@%(20),Z@%(21), \
      \ Z@%(22),Z@%(23),Z@%(24),Z@%(25),Z@%(26),Z@%(27),Z@%(28),Z@%(29),Z@%(30),Z@%(31),Z@%(32),Z@%(33),Z@%(34),Z@%(35),Z@%(36),Z@%(37),Z@%(38),Z@%(39),Z@%(40),Z@%(41),Z@%(42),Z@%(43),Z@%(44),Z@%(45),Z@%(46), \
      \ Z@%(47),Z@%(48),Z@%(49),Z@%(50),Z@%(51),Z@%(52),Z@%(53),Z@%(54),Z@%(55),Z@%(56),Z@%(57),Z@%(58),Z@%(59),Z@%(60),Z@%(61),Z@%(62),Z@%(63),Z@%(64),Z@%(65),Z@%(66),Z@%(67),Z@%(68),Z@%(69),Z@%(70),Z@%(71), \
      \ Z@%(72),Z@%(73),Z@%(74),Z@%(75),Z@%(76),Z@%(77),Z@%(78),Z@%(79),Z@%(80),Z@%(81),Z@%(82),Z@%(83),Z@%(84),Z@%(85),Z@%(86),Z@%(87),Z@%(88),Z@%(89),Z@%(90),Z@%(91),Z@%(92),Z@%(93),Z@%(94),Z@%(95),Z@%(96): RETURN
      s@ys%+=0 : IF S%<>!400 s@ys%=S%    :REM Save a previous ON SYS vector as a global variable.
      =NOT D% AND &7FFF

      REM MSDN limits IDs to &7FFF

      ;
      DEF PROC_DoEvents
      REM Event handler in main program loop.
      LOCAL P%, M%,W%,L%,K%, E%()
      DIM E%(2)
      WHILE Z@%(0) :REM Z@%(0) is 0 if no event in the queue otherwise it points to the interrupt parameter list @msg%, @wparam% and @lparam%
        E%()=Z@%(Z@%(0)-2),Z@%(Z@%(0)-1),Z@%(Z@%(0))     :REM get all data from queue in one statament to maintain synchronism with no data loss.
        Z@%(0) -= 3                                      :REM Adjust pointer to next interrupt in queue, if any
        M%=E%(0):W%=E%(1):L%=E%(2)                       :REM Extract @msg%, wparam% and lparam%
        CASE M% OF
          WHEN 3,5,276,277:P%=F@%(7) :IF P% PROC(^P%)(M%,W%,L%)               :REM Move/Size handler.
          WHEN 513,515,516,518,519,521:P%=F@%(6) :IF P% PROC(^P%)(M%,W%,L%)   :REM Mouse handler
          WHEN 275:P%=F@%(5) :IF ?P%=&28 PROC(^P%)(W%) ELSE PROC(^P%)         :REM Timer handler
          WHEN 273,78:                                                           REM Control event notifications
            IF W%=2 IF F@%(2) THEN
              P%=F@%(2):IF ?P%=&28 PROC(^P%)(M%,W%,L%) ELSE PROC(^P%)         :REM trap ID_CANCEL in WINLIB2B mode. Destination set by PROC_trapcancel
              ENDPROC                                                         :REM We have handled it with a special PROC for cancel button so exit.
            ENDIF
            IF W%=1 IF F@%(1) THEN
              P%=F@%(1):IF ?P%=&28 PROC(^P%)(M%,W%,L%) ELSE PROC(^P%)         :REM trap ID_OK, optional.
              ENDPROC                                                         :REM We have handled it with a special PROC for OK button so exit.
            ENDIF
            P%=F@%(NOT W% AND &3FF)                           :REM get event handler using the control ID as the index into the handler address array.
            IF P%=0 OR W%<0 THEN
              IF s@ys% SYS @fn%(18),s@ys%,M%,W%,L% :@flags% OR= &20000000   :REM Unhandled controls or notifications passed back to a previous ON SYS if it exists.
              ENDPROC  REM Eliminates NM_CUSTOMDRAW and other Notifications &FXXX XXXX.  e.g BCN_
            ENDIF
            IF ?P%=&28 PROC(^P%)(M%,W%>>16,L%) ELSE PROC(^P%) :REM Jump to PROC. Don't send the ID part of W% as it is hidden from the user.
          OTHERWISE
            REM The other *SYS 1 Events not handled elsewhere. IF PROC_initsweeper not called they get passed onto an earlier ON SYS if it exists.
            P%=F@%(4) :IF P% PROC(^P%)(M%,W%,L%) ELSE IF s@ys% SYS @fn%(18),s@ys%,M%,W%,L% :@flags% OR= &20000000
        ENDCASE
      ENDWHILE
      WAIT 0   :REM Short delay so we don't need one in main loop.
      P%=F@%(3)
      IF P% THEN
        REM Look for keystrokes
        K%=INKEY(0)
        IF K%<>-1  PROC(^P%)(K%)
      ENDIF
      ENDPROC

 


How to use this is in the next post as it won't all fit.
« Last Edit: Apr 11th, 2016, 8:45pm by Zaphod » User IP Logged

Zaphod
Guest
xx Re: Alternative Event library.
« Reply #1 on: Apr 11th, 2016, 4:10pm »

Part 2
How do we use this library?

We register or link the event to a handler procedure by calling FN_regproc(PROChandler)
Say we want a series of menu buttons to initiate some action PROCs.
Code:
      INSTALL @lib$+"E2lib"  :REM Event handling. OR add "library" code to the program
      REM Set up menus
      AM$ = "AppendMenu"
      SYS "CreateMenu" TO H%
      SYS "CreatePopupMenu" TO hpopfile%
      SYS AM$, hpopfile%, 0, FN_regproc(PROCnew), "&New"
      SYS AM$, hpopfile%, 0, FN_regproc(PROCopen), "&Open"
      SYS AM$, hpopfile%, 0, FN_regproc(PROCsave), "&Save"
      SYS AM$, hpopfile%, 0, FN_regproc(PROCsaveas), "Save &As"
      SYS AM$, hpopfile%, &800, 0, 0
      SYS AM$, hpopfile%, 0, FN_regproc(PROCprint), "&Print"+CHR$9+"Ctrl+P "
      SYS AM$, hpopfile%, &800, 0, 0
      SYS AM$, hpopfile%, 0, FN_regproc(PROCclose), "E&xit"
      SYS AM$, H%, 16, hpopfile%, "&File      "
      SYS "SetMenu",@hwnd%,H%
      SYS "DrawMenuBar",@hwnd%

      REPEAT
        REM All actions from Interrupts. 
        PROC_DoEvents  :REM This does it all.
      UNTIL FALSE
      END

DEF PROCnew :PRINT "New"
DEF PROCopen :PRINT "Open"
DEF PROCsave :PRINT "Save me"
DEF PROCsaveas :PRINT "OK, you have the idea by now!"
DEF PROCprint  :PRINT "Offline!"
DEF PROCclose : QUIT
ENDPROC
 

As you see the declaration is just like FN_setproc but the execution is similar to Eventlib's PROC_eventregister and with an event queue polling loop like Eventlib. This mechanism takes away all the limitation and warnings that you will see about interrupt handling. You cannot get re-entrant interrupts and be surprised by interrupts changing variable contents between program statements. (Yes that can happen with interrupts and poorly written code! And it is a bastard to debug.)
Like in earlier Eventlib versions you can add the other event "ON" types and direct them into the same event queue and define a handler. The latest Eventlib merges all events and is an opt out rather than opt in system which is not really suited to the approach used here. So to add mouse events to the queue you would call:
Code:
PROC_initonmouse(PROCmousehandler())

DEF PROCmousehandler(m%,w%,l%) 
 

where m%, w% and l% are the usual windows message parameters @msg%, @wparam% and @lparam% for that event. You would not use an ON MOUSE statement, and if you did put one after this PROC_initmouse() it would decouple the mouse events and send them to the new ON MOUSE line.

Similarly for ON TIME, and ON MOVE. The event messages that appear in the original ON MOUSE, for instance, all go to your one mouse handler. You don't need to separate out each message number and register separate handlers as you would in Eventlib. For ON MOVE you could write something like this:
Code:
PROC_initonmove(PROCmove())
 

and the handler would be something like: Code:
 
      DEF PROCmove(m%,w%,l%)
      CASE m% OF
        WHEN WM_MOVE: PRINT "Move"
        WHEN WM_SIZE: PRINT "Size" :SYS "PostMessage", hstat%, m%, w%, l%
        WHEN WM_HSCROLL: PRINT "Hscroll", ~w%
        WHEN WM_VSCROLL:
      ENDCASE
      ENDPROC
 

That is probably the same code you had for ON MOVE anyway but now the events are going to occur in the right sequence and you are less likely to "lose" events and get stuck with a window of the wrong size or position if you drag it. Having an event queue helps a lot with ON MOVE type events.

There are a couple of additions, the first is to link the keyboard so that it looks like it is event driven, but is just picking up keystrokes from the keyboard buffer, and it is convenient to have the same mechanism retrieve keystrokes.

PROC_initkb(PROCkey()) links that into the queue. (Well it looks like it does, but real events have priority.) Now each time a key is pressed PROCkey(k%) will be executed and k% is the INKEY key code. Note that most of the time you will not get keystroke "events" as they will be directed to or captured by any control or window that has focus, which is the normal Windows operation. But if you did stop with the main window having focus then the keystrokes come via this link and you don't need a separate INKEY loop.

And there are a couple of other additions to help processing of windows "OK" and "Cancel" buttons. These are often given ID's of 1 and 2 respectively. The methods used here, as with FN_setproc and Eventlib, hide the control’s true ID and that can present some difficulties especially with WINLIB2B. We have two procedures to dedicate handlers to those two buttons. Their use is optional but without PROC_cancel it is difficult to handle a Dialog system close button in WINLIB2B and they are convenient and pre-defined for buttons that you will probably want to use anyway.
Code:
 PROC_trapok(PROCok)
PROC_cancel(PROCcancel) 


Just to wrap it up there is also PROC_initsweeper(PROCothers()) This is the router for a few remaining miscellaneous events that *SYS 1 can throw up. If you don't use *SYS 1 then nothing comes this way so don't bother with it. It handles this batch of events:
Code:
 
      DEF PROCothers(m%,w%,l%)
      REM collect remaining non-control interrupts
      CASE m% OF
        WHEN 287: PRINT "WM_MENUSELECT", ~(w% AND &FFFF),~(w%>>16), l%
        WHEN 279: PRINT "WM_INITMENUPOPUP", w%
        WHEN 278: PRINT "WM_INITMENU", w%
        WHEN 123: PRINT "WM_CONTEXTMENU", w%
        WHEN 83:  PRINT "WM_HELP"
        WHEN 786: PRINT "WM_HOTKEY"
        WHEN 563: PRINT "WM_DROPFILES"
      ENDCASE
      ENDPROC
 

One last note. With control events the middle parameter of say PROCbutton(m%,w%,l%) is a filtered version of @wparam% and only has the notification code if any is passed on. The high word is the control ID which the code issues and is not generally useful. If you need to “talk” to the control the l% contains the control handle usually and this is a more direct route. You can use
SYS”SendMessage”,l%, Message, wpram, lparam
rather than
SYS”SendDlgItemMessage” !dlg%, ID, Message, wparam, lparam

Generally with the handler procedures if you don’t need to know anything except that the control triggered an event then you don’t need to have a parameter list.
This is what we had in the menu example at the start, as the other data in the @msg%, @wparam% and @lparam% are probably of no interest. The same is probably true for simple buttons but for a Combobox, Listbox or Edit you would probably want to know about the particular events such as a change of selection and so on.

Is that enough to wet anyone's appetite?
There is more that can go along with this such as a menu handler that generates accelerator keys automatically that you probably never knew you needed until you see them in action. I don't think I have ever seen an article on accelerator keys and the only programs I have seen them in are Richard's, no surprise there!

Any questions? Some of the code is pretty difficult to understand but there are Wiki entries on Event queues that may help. (Or not, as the case might be!)
User IP Logged

hellomike
New Member
Image


member is offline

Avatar




PM

Gender: Male
Posts: 46
xx Re: Alternative Event library.
« Reply #2 on: Apr 11th, 2016, 6:24pm »

Hi Z,

That is some impressive stuff you have constructed. So I was eager to just see it in action and saved the code from Part#1 as "E2LIB.BBC" in the library folder (where the others live as well).
Then I copied the main program example and try to run it.

I see the menu but then the program ends immediately, i.e. I see the '>' prompt. It seems that something in the PROC_DoEvents body makes the program stop.
There is no error message.

The second remark I like to make is about the global static variable E%.
Isn't this variable altered when the programmer used either 'PROC_initkb()', 'PROC_trapcancel()' or 'PROC_trapok()'?

Let me know what I miss or what's wrong please.

Thanks

Mike
User IP Logged

Zaphod
Guest
xx Re: Alternative Event library.
« Reply #3 on: Apr 11th, 2016, 8:27pm »

Hellomike, you are absolutely right, that E% should have been set as local. Thanks for pointing that out.

And the version of the library as posted does the same for me. How embarrassing!

It turns out that in posting I left off the final ENDPROC so it just stopped as it ran out of code.

I have fixed the error with E% and added the ENDPROC to the listing and hope that it now works.

Thanks for your input.


« Last Edit: Apr 11th, 2016, 8:47pm by Zaphod » User IP Logged

Zaphod
Guest
xx Re: Alternative Event library.
« Reply #4 on: Apr 12th, 2016, 02:01am »

Another little example. This demonstrates the use of the PROC_trapxx procedures to handle the OK and Cancel and process the system close box when using WINLIB2B.
You can do some processing and then close the dialog. It will work just the same with WINLIB2.
Code:
      INSTALL @lib$+"WINLIB2"
      INSTALL @lib$+"E2lib" :REM or copy the event lib code to this program.
      Dlgt1% = FN_newdialog("Test Polling",125,40,153,61,8,400)
      PROC_pushbutton(Dlgt1%,"Push",FN_regproc(PROCpush),22,12,40,14,0)
      PROC_pushbutton(Dlgt1%,"OK",1,22,40,40,14,&20001)
      PROC_pushbutton(Dlgt1%,"Cancel",2,89,40,42,14,&0)
      PROC_showdialog(Dlgt1%)

      PROC_trapok(PROCok)
      PROC_trapcancel(PROCcancel)

      REM Polling
      REPEAT
        PROC_DoEvents
      UNTIL FALSE
      PROC_closedialog(Dlgt1%)
      END

      DEF PROCpush
      PRINT "Push pushed"
      ENDPROC

      DEF PROCok
      PRINT "OK pushed"
      ENDPROC

      DEF PROCcancel
      PRINT "Cancel pushed, we will close shortly."
      WAIT 200
      PROC_closedialog(Dlgt1%)
      QUIT
      ENDPROC
 

User IP Logged

Pages: 1  Notify Send Topic Print
« Previous Topic | Next Topic »

| |

This forum powered for FREE by Conforums ©
Terms of Service | Privacy Policy | Conforums Support | Parental Controls