What will we cover?
So far we have been looking at batch oriented programs. Recall that programs can be batch oriented, whereby they start, do something then stop, or event driven where they start, wait for events and only stop when told to do so - by an event. How do we create an event driven program? We'll look at this in two ways - first we will simulate an event environment then we'll create a very simple GUI program that uses the operating system and environment to generate events.
Every event driven program has a loop somewhere that catches received events and processes them. The events may be generated by the operating environment, as happens with virtually all GUI programs or the program itself may go looking for events as is often the case in embedded control systems such as used in cameras etc.
We will create a program that looks for precisely one type of event - keyboard input - and processes the results until some quit event is received. In our case the quit event will be the space key. We will process the incoming events in a very simple manner - we will simply print the ASCII code for that key. We'll use Python for this because it has a nice, easy to use function for reading keys one at a time - getch(). This function comes in two varieties depending on the operating system you use. If you are using Linux it's found in the curses module, if you use Windows it's in the msvcrt module. I'll use the Windows version first then I'll discuss the Linux option in more detail. You will need to run these programs from an OS command prompt since any IDE, like IDLE, being a GUI, will capture the keystrokes differently.
First we implement the event handler functions that will be called when a keypress is detected then the main program body which simply starts up the event gathering loop and calls the appropriate event handling function when a valid event is detected.
import msvcrt import sys # First the event handlers def doKeyEvent(key): if key == '\x00' or key == '\xe0': key = msvcrt.getch() print ( ord(key), ' ', end='') sys.stdout.flush() # make sure it appears on screen def doQuit(key): print() # force a new line raise SystemExit # first clear some screen space lines = 25 for n in range(lines): print() # Now the main event-loop while True: ky = msvcrt.getch() if len(str(ky)) != 0: # we have a real event if " " in str(ky): doQuit(ky) else: doKeyEvent(ky)
Notice that what we do with the events is of no interest to the main body, it simply collects the events and passes them to the event handlers. This independence of event capture and processing is a key feature of event driven programming.
Note 1: getch() returns bytes so we need to convert them to a string and since the resulting string is not simply the letter we need to use the in test to check for the quit condition.
Note 2: Where the key was non ASCII - a Function key for example - we needed to fetch a second character from the keyboard, this is because these special keys actually generate pairs of bytes and getch only retrieves one at a time. The actual value of interest is the second byte.
Linux and MacOS X programmers can't use the msvcrt library so must use another module called curses instead. The resultant code is very similar to the windows code but there are a few modifications required, as shown below:
import curses as c def doKeyEvent(key): if key == '\x00' or key == '\xe0': # non ASCII key key = screen.getch() # fetch second character screen.addstr(str(key)+' ') # uses global screen variable def doQuitEvent(key): c.resetty() # set terminal settings c.endwin() # end curses session raise SystemExit # clear the screen of clutter, save current settings, stop characters auto # echoing to screen and then tell user what to do to quit screen = c.initscr() c.savetty() c.noecho() screen.addstr("Hit space to end...\n") # Now mainloop runs "forever" while True: ky = screen.getch() if ky != -1: # send events to event handling functions if ky == ord(" "): # check for quit event doQuitEvent(ky) else: doKeyEvent(ky) c.endwin()
You'll see that the usual print commands don't work under curses and instead we have to use curses own screen handling functions. Further, curses getch returns -1 when there is no key pressed rather than an empty string. Aside from that the logic of the program is identical to the Windows version.
Note that the curses.endwin() should restore your screen to normal but in some cases it may not work completely. If you wind up with an invisible cursor, no carriage return etc. You can fix it if you exit Python with Ctrl-D and use the Linux command:
$ stty echo -nl
That's 'nl' for NewLine by the way, not n-one. Hopefully that will restore things to normal.
If we were creating this as a framework for use in lots of projects we would probably include a call to an initialization function at the start and a cleanup function at the end. The programmer could then use the loop part and provide his own initialization, processing and cleanup functions.
That's exactly what most GUI type environments do, in that the loop part is embedded in the operating environment or framework and applications are contractually required to provide the event handling functions and hook these into the event loop in some way.
Let's see that in action as we explore Python's Tkinter GUI library.
For this exercise we'll use the Python Tkinter toolkit. This is a Python wrapper around the Tk toolkit originally written as an extension to the Tcl programming language ()and also available for Perl and Ruby). The Python version is an object oriented framework which is, in my opinion, considerably easier to work with than the original Tk version. We will look much more closely at the principles of GUI programming in the GUI topic.
I am not going to dwell much on the GUI aspects in this topic, rather I want to focus on the style of programming - using Tkinter to handle the event loop and leaving the programmer to create the initial GUI and then process the events as they arrive.
In the example we create an application class KeysApp which creates the GUI in the __init__ method and binds the space key to the doQuitEvent method. The class also defines the required doQuitEvent method.
The GUI itself simply consists of a text entry widget whose default behavior is to echo characters typed onto the display.
Creating an application class is quite common in OO event driven environments because there is a lot of synergy between the concepts of events being sent to a program and messages being sent to an object. The two concepts map on to each other very easily. An event handling function thus becomes a method of the application class.
Having defined the class we simply create an instance of it and then send it the mainloop message.
The code looks like this:
# Use from X import * to save having to preface everything # as tkinter.xxx from tkinter import * import sys # Create the application class which defines the GUI # and the event handling methods class KeysApp(Frame): def __init__(self): # use constructor to build GUI super().__init__() self.txtBox = Text(self) self.txtBox.bind("<space>", self.doQuitEvent) self.txtBox.pack() self.pack() def doQuitEvent(self,event): sys.exit() # Now create an instance and start the event loop running myApp = KeysApp() myApp.mainloop()
Note: If you run this from inside IDLE you will find the program doesn't close properly but simply prints an exit message in the shell window. Don't worry, that's just IDLE trying to be helpful. If you run it from a command prompt everything should be just fine.
Notice that we don't even implement a key event handler! That's because the default behavior of the Text widget is to print out the keys pressed. However that does mean our programs are not really functionally equivalent. In the console version we printed the ASCII codes of all keys rather than only printing the alphanumeric versions of printable keys as we do here. There's nothing to prevent us capturing all of the keypresses and doing the same thing. To do so we would add the following line to the __init__ method:
And the following method to process the event:
def doKeyEvent(self,event): str = "%d\n" % event.keycode self.txtBox.insert(END, str) return "break"
Note 1: the key value is stored in the keycode field of the event. I had to look at the source code of Tkinter.py to find that out... Recall that curiosity is a key attribute of a programmer?!
Note 2: return "break" is a magic signal to tell Tkinter not to invoke the default event processing for that widget. Without that line, the text box displays the ASCII code followed by the actual character typed, which is not what we want here.
That's enough on Tkinter for now. This isn't meant to be a Tkinter tutorial, that's the subject of the next topic. There are also several books on using Tk and Tkinter.
<form name='entry'> <p>Type value then click outside the field with your mouse</p> <input Type='text' Name='data' onChange='alert("We got a value of " + document.entry.data.value);'/> </form>
Although our function simply called alert it could have done much more. In fact, it could have been a complex program in its own right, whatever we chose to put inside the function body.
<script type="text/vbscript"> Sub EchoInput() MsgBox "We got a value of " & Document.entry2.data.value End Sub </script> <form name='entry2'> <p>Type value then click outside the field with your mouse</p> <input Type='text' Name='data' onChange='EchoInput()'/> </form>
Thus we can see that web browser code can be written in batch form or event driven form or a combination of styles to suit our needs.
Things to remember