Callback-based style

The callback-based style of event programming is one of the most common styles of event-driven programming. In this style, objects that emit events are the ones that are responsible for defining their event handlers. This means a one-to-one or (at most) many-to-one relation between event emitters and event handlers.

This style of event-based programming is the dominating pattern among GUI frameworks and libraries. The reason for that is simple – it really captures the way how both users and programmers think about user interfaces. Every action we do, whether we toggle a switch, press a button, or tick a checkbox, we do it usually with a clear and single purpose.

We've already seen an example of callback-based event-driven programming and discussed an example of a graphical application written using the tkinter library (see the Event-driven programming in GUIs section). Let's recall one line from that application listing:

zen_button = Button(root, text="Python Zen", command=show_zen)

The previous instantiation of the Button class defines that the show_zen() function should be called whenever the button is pressed. Our event is implicit, and the show_zen() callback (in tkinter, callbacks are called commands) does not receive any object that would encapsulate the event that invoked its call. This makes sense, because the responsibility of attaching event handlers lies closer to the event emitter (here, it is the button), and the event handler is barely concerned about the actual occurrence of the event.

In some implementations of callback-based event-driven programming, the actual binding between event emitters and event handlers is a separate step that can be performed after the event emitter is initialized. This style of binding is possible in tkinter too, but only for raw user interaction events. The following is the updated excerpt of the previous tkinter application that uses this style of event binding:

def main_window(root):
frame = Frame(root, width=100, height=100)

zen_button = Button(root, text="Python Zen")
zen_button.bind("<ButtonRelease-1>", show_zen)
zen_button.pack()


def show_zen(event):
messagebox.showinfo(
"Zen of Python",
this.s.translate(rot13)
)

In the preceding example, the event is no longer implicit, so the show_zen() callback must be able to accept event object. It contains basic information about user interaction, such as the position of the mouse cursor, the time of the event, and the associated widget. What is important to remember is that this type of event binding is still unicast. This means that one event (here, <ButtonRelease-1>) from one object (here, zen_button) can be bound to only one callback (here, show_zen()). It is possible to attach the same handler to multiple events and/or multiple objects, but a single event that comes from a single source can be dispatched to only one callback. Any attempt to attach a new callback using the bind() method will override the new one.

The unicast nature of callback-based event programming has obvious limitations as it requires the tight coupling of application components. The inability to attach multiple fine-grained handlers to single events often means that every handler is specialized to serve a single emitter and cannot be bound to objects of a different type.

Let's take a look at subject-based style in the next section.