allegro-cl archives 1997-1-1 | home index prev H thread prev K thread-next J next L |
From: cheetha (Ken Cheetham) Subject: Re: [pcspr4288] Custom event handling of a widget Date: 1997-1-1 1:41 Does anyone know how to define the keyboard/mouse behavior of a widget, say, single-item-list? What I want to do is to add functions for mouse left single click, left double click and right single click. We appreciate everyone's comments regarding event handling for widgets (controls) in Allegro CL for Windows. We are investigating making the programmatic interface more consistant and improving the documentation for the next major release. We especially need to explicitly list the set of event handlers (and properties) that are defined for each type of control, and then document all event handlers and properties in a single place rather than each one under some arbitrary type of widget for which it is defined. Part of the confusion, though, stems from the fact that most of the controls are built into the OS with special behavior and are not intended to (typically) be handled in the same low-level way as "regular" windows. While coming up with an explanation for this I've ended up writing a whole primer (below), which I'll submit for official documentation. You will probably need to refer to subclass-widget toward the end of this for intercepting arbitrary single clicks on an item-list widget. [This request has been assigned the tracking number pcspr4288. For efficiency, please mention this number in the title of future messages concerning it. Also please cc: <franz.com at pc-support> so that someone else can handle your message if I am away.] Ken Cheetham <franz.com at cheetham> Franz Inc. Voice: (510) 548-3600 1995 University Avenue, Suite 275 Fax: (510) 548-8253 Berkeley, CA 94704 http: //www.franz.com/ ACL Windows FAQ: ftp.franz.com:/pub/acl4w-faq ACL Unix FAQ: ftp.franz.com:/pub/faq --------------------------------------------------------------------------- Handling Widget Events For regular non-widget windows, Allegro CL receives a variety of low-level messages (or events). All mouse click, mouse movement, and keypress messages are passed to the EVENT method for the window, while other messages are passed to particular methods that are set up for them, such as the window's SET-FOCUS method which is called whenever the window receives the keyboard focus. For widgets (also known as controls), on the other hand, Allegro normally does not receive these low-level events. Most of these controls are defined in the Windows OS, and the low-level window events are sent directly to the controls' own event handlers within the OS, and each control sends a smaller higher-level set of messages to the application (Allegro) as defined by the particular control. For example, when the user clicks on an item-list, Allegro does not receive any mouse click message, and so we cannot call EVENT for either the widget or its dialog-item-window. Instead the control internally receives the event and determines which item was selected and so on, and then sends Allegro a "change" message if the click results in a different item in the list being selected, and so we invoke the set-value-fn of the widget at that time. A special way to intercept all low-level messages for controls is described further below, but first we will discuss the higher-level event-handling interface that each control defines for itself, as implemented in Common Graphics. --------------------------------------------------------------------------- Event Handlers On Widget Instances Controls typically send out high-level messages to indicate such occurrences as when their value changes, when they receive the keyboard focus, and so on. For many of these higher-level messages, Common Graphics allows you to assign an arbitrary function to be run whenever a particular widget message is received. For example, an item-list does not send us a message for a single click, but it DOES send a message for a double-click, and so you can set up a function to run whenever item-list-1 is double-clicked: (defun my-double-click-fn (dialog widget) (print "You successfully double-clicked!")) (setf (dialog-item-double-click-fn item-list-1) 'my-double-click-fn) This technique is object-oriented, but you may notice that it is not CLOS-style since it locally attaches a "method" to a particular object, while a CLOS method is independent of any particular objects (and may even "specialize" different parameters on different classes or objects, a feature known as multimethods). We still provide these instance-based "slot methods" for widget event handlers, though, because (1) widget instances typically have their own individual event handlers, and it would be inconvenient to specialize CLOS methods on each one, and (2) such "slot methods" can be readily manipulated with interface builders such as ours, which work primarily with individual controls. A specific point of confusion is that there is not always a one-to-one correspondence between widget messages and the Common Graphics handlers for them. Two cases in particular: (1) when an editable-text widget has the "delayed" attribute (as it does by default), its set-value-fn is run when it loses the keyboard focus rather than when the widget sends us a "change" message as each character is typed, and (2) clicking a button widget invokes its set-value-fn even though the button's "value" is not meaningful and is toggled between t and NIL as it is clicked simply to cause the set-value-fn to be invoked. It would have been more sensible to provide a specific handler for the button-click rather than to artificially map the click onto the general set-value-fn, but that feature may be difficult to enhance in a backwardly-compatible way. --------------------------------------------------------------------------- Event Handlers On Widget Classes While the above instance-based event-handling should usually suffice, Allegro allows you to alternately define CLOS methods to handle particular events for all controls of some subclass. In order to do this, you first need to create a (CLOS) subclass of some widget class. This is somewhat awkward due to the design where (1) a dialog-item and its dialog-item-window are separate objects and (2) events are passed through the dialog-item-window rather than the dialog-item itself. Therefore you need to subclass both of those classes and then add a special method that points one to the other. (We are looking into combining these two classes into a single class, which would make this more straighforward.) This example creates a subclass of single-item-list and its associated dialog-item-window class: (defclass my-item-list (single-item-list)()) (defclass my-item-list-pane (pc::single-item-list-pane)()) (defmethod widget-device ((item my-item-list)(dialog basic-pane)) 'my-item-list-pane) This method then defines a double-click handler for all instances of the my-item-list subclass: (defmethod dialog-item-double-click ((dialog t)(widget my-item-list)) (format t "You double-clicked ~a" (object-name widget)) (call-next-method)) Since the default dialog-item-double-click method simply invokes the widget's personal dialog-item-double-click-FN (if it has one), the call-next-method above would invoke that instance's own double-click handler as well. --------------------------------------------------------------------------- Intercepting Low-Level Events on Widgets So far we have discussed handling the high-level events that are defined by a particular control, either at the instance level (simple) or at the class level (somewhat awkward). This higher-level interface to the built-in controls is recommended whenever it is sufficient. But if you really need to intercept the low-level mouse or keyboard events for a widget, then you can call (subclass-widget a-widget-instance) Subclass-widget has nothing to do with CLOS subclasses, but rather performs a standard Windows technique where we tell the OS to send all low-level events for a control to Allegro's event handler instead of to the control's own event handler. (Windows imaginatively regards this as "subclassing".) This technique allows Allegro to intercept all events and then optionally pass them on to the control's own event handler in the OS if we want to retain the usual effect that's built into the control. Once you have called subclass-widget on a widget instance, then the EVENT method will be called for each low-level mouse and keyboard event for the widget. (Other low-level events will be passed only to the pc:control-procedure generic function, which is similar to the pc:window-procedure generic function through which all non-control messages are passed.) You might want to go this route if, for example, you really need to know when the user has clicked the item that is already selected in an item-list, since the control sends us no high-level message in that case. Note that widgets of type pc::lisp-widget-mixin are written entirely in lisp using "regular" (non-control) windows, and so the dialog-item-window of the widget receives the low-level window events already. So there is no need to call subclass-widget on a lisp-widget in order to receive these events. Since the EVENT method is called on the dialog-item-window of a "Windows subclassed" widget, you could intercept all left-clicks on the my-item-list subclass defined above like this: (defmethod event ((window my-item-list-pane)(message (eql mouse-left-down)) buttons data time) (format t "You clicked on ~a at position ~a,~a~%" (object-name (window-dialog-item window)) (position-x data)(position-y data)) ;; This call-next-method passes the event back to the control's own ;; event handler built into the OS so that it will select the ;; clicked list item as usual. Removing this line would prevent ;; the OS from selecting the clicked item. (call-next-method)) (subclass-widget a-my-item-list-instance) Note that the "data" parameter above indicates the mouse position at the time of the click. This meaning is specific to mouse-click messages, and the data parameter means other things for keypress and mouse movement messages. (In the next major release we may replace EVENT with individual generic functions for the various mouse and keyboard messages (though retaining EVENT for backward compatibility) so that the meaning of the function parameters is more explicit. |