• Non ci sono risultati.

Wrapping up OCaml: Designing a GUI Library

18.12 Listeners and Notifiers

repaint = (fun (g:Gctx.t) -> Gctx.draw_string g !lbl);

size = (fun (g:Gctx.t) -> Gctx.text_size g !lbl);

handle = (fun (g:Gctx.t) (e:Gctxt.event) -> ()) },

{set_label = (fun (r:string) -> lbl := r)})

Now when we call the label function, it returns a pair consisting of a widget and a label controller that can be used to change the string dis-played by the label.

More generally, any stateful widget will have its own kind of controller that can be used to modify the widgets state.

18.12 Listeners and Notifiers

Different widgets may need to react to different kinds of events. For ex-ample, a button needs to “listen” for mouseclick events and then run some appropriate code; a scrollbar might have to “listen” for mouse drag events (mouse motion with the button down); a textbox widget might have to

“listen” for key press events. Moreover, the way that a button (or other widget) responds to a certain event might change depending on applica-tion context. In general, whether or not a widget should “listen” for a particular event might also depend on application-specific state.

How can we easily capture the wide variety of possible ways that a widget might want to interact with user-generated events? How can we modularly add or remove code for processing events? Our solution (which is based on Java’s Swing library) is to introduce a new kind of wid-get called a notifier. The idea is that a notifier widwid-get “eavesdrops on” or

“listens to” the events flowing through a part of the widget hierarchy. It manages a list of event listeners, which are a bit likehandle functions ex-cept that they don’t really participate in routing events through the widget hierarchy—they simply listen for a particular kind of event, react to it, and then either propagate the event or stop it from being further processed.

Figure 18.7 shows pictorially, how we might add anotifierwidget to the

“Hello World” example.

!"#$%&%'#(

3456789(:*!!(7866(

;-'+%'(

<=*"'(

;-'+%'(

!*;%!(

<=*"'(

#=*1%( ;-'+%'(

!*;%!(

>"+?%$($'%%(

Hello! World!

@&($<%(#1'%%&(

&-./%'( !6(AA(!7(AA(!B(AA(CD((

E#%'(1!"1F#G(

?%&%'*.&?(

%H%&$(%(

Figure 18.7: A notifier widget maintains in its local state a list of “listeners” that get a chance to process events that flow through the widget tree. Which listeners are associated with the notifier can be changed using a notifier controller.

Notifiers, like the stateful version of label widgets discussed above, maintain some local state—in this case a list of event_listener objects.

Thus, each notifier widget comes equipped with a notifier_controller

that can be used to modify the list of listeners. For simplicity in the code below, the notifier_controller only allows new event_listener

objects to be added to the notifier; it is easy to extend this idea to allow

event_listeners to be removed as well.

These design criteria lead us to create a type forevent_listener func-tions that looks like this:

type event_listener_result =

| EventFinished

| EventNotDone type event_listener =

Gctx.t -> Gctx.event -> event_listener_result

An event_listener is just a function that, like a handle method of a widget, takes a Gctx.tandGctx.eventand processes the event. Unlike a handler, which always returns unit, an event_listenercan return either

EventFinished, which signals to thenotifierthat the event should not be further processed by listeners that are “down stream” in the widget tree, orEventNotDone, which signals that the event should be propagated.

Once we have defined the type of event_listeners, it is straightfor-ward to define the behavior of thenotifierwidget and itsnotifier_controller:

(*

A notifier_controller is associated with a notifier widget.

It allows the program to add event listeners to the notifier.

*)

type notifier_controller = {

add_event_listener: event_listener -> unit }

(*

A notifier widget is a widget "wrapper" that doesn’t take up any extra screen space -- it extends an existing widget with the ability to react to events. It maintains a list of

"listeners" that eavesdrop on the events propagated through the notifier widget.

When an event comes in to the notifier, it is passed to each listener in turn until one of them declares the event

to be "finished".

*)

let notifier (w: t) : t * notifier_controller = let listeners = ref [] in

{ repaint = w.repaint;

handle =

(fun (g:Gctx.t) (e: Gctx.event) ->

let rec loop (l: event_listener list) : unit = begin match l with

| [] -> w.handle g e

| h::t -> begin match h g e with

| EventFinished -> ()

| EventNotDone -> loop t end

fun newl -> listeners := newl::!listeners }

With this infrastructure in place, it is easy to define specialized event listeners that react to particular kinds of events. For example, it is useful to define a mouseclick_listener, parameterized by an action to perform

when the mouse is clicked:

(* Performs an action upon receiving a mouse click. *)

let mouseclick_listener (action: unit -> unit) : event_listener = fun (g:Gctx.t) (e: Gctx.event) ->

if Gctx.button_pressed g e then (action (); EventFinished) else EventNotDone

Amouse_listener, in contrast, exposes another convenient way of re-acting to mouse events—it unpacks the mouse button and position infor-mation from an event e and passes that data to an action function. A client application like the Paint program might add amouse_listenerto a

canvaswidget to allow the canvas to react to user-generated mouse events on the canvas.

(*

A mouse_listener takes an action that responds to mouse events of all kinds - the action determines what to do if the mouse button is down and the mouse cursor is at a particular location

*)

let mouse_listener

(action : bool -> int*int -> event_listener_result) : event_listener =

fun (g:Gctx.t) (e:Gctx.event) ->

action (Gctx.button_pressed g e) (Gctx.event_pos g e)

18.13 Buttons (at last!)

Our GUI library finally has enough functionality to implement a tra-ditional button widget: A button is just a label widget wrapped in a notifier. The resulting widget has both a label_controller and a

notifier_controller, which can be used to change the state of the but-ton.