Controllerless Keyboarding
For the last two releases, most of the "custom" widgets we've added to the system are done in a way different than classical VisualWorks widgets. Examples are the new comparison tool, the store feedback widgets, the bundle structure tool, and the prerequisite tool. These have served as a testground to try out some different approaches than classically found in VisualWorks. One the questions that comes up from some colleagues is "so, if you're doing widgets a new way, what is that way?"
The rest of this post is about one aspect of that "different" way. As I've wandered far and wide through other widget frameworks, I've learned that a) "MVC" is very popular and b) for every different framework there is a new and unique interpretation of "controller." One of the "different" ways I've been building widgets is without controllers. There are two aspects to deal with in this "controllerless" new world. One is how we deal with mouse interaction, the other keyboard interaction. The aspect of interest here today is keyboard interaction.
The original VisualWorks widget framework based on MVC, sometimes moniker'ed "wrapper," didn't have a keyboard focus concept. What ever widget was below the mouse and was willing, was where your keyboard input went. Round about version 2.0 of VisualWorks (then an add-on product to ObectWorks 4.1), the idea of keyboard focus got added. Which is a long winded way of saying, I didn't design this, I just learned how to work it. To have a controllerless keyboard savvy widget, you should add the following methods to your VisualPart subclass. The example code is what the AbstractComparisonRollupView does with them, as examples.
- view - The object responsible for managing keyboard events (KeyboardProcessor) assumes it's got an object in the role of controller and it can send view to it. Well the whole point is that your view is playing the role of both the controller and view.
view ^self
- controller - Same sort of story as above, just from the other direction.
controller ^self
- desiresFocus - This shoud return a boolean. The KeyboardProcessor sends this method to a keyboard consuming object when it's trying to determine if you'd like to be tabbed into. You can simply return true, or do something more complex, such as filter based on whether the receiver is "enabled."
desiresFocus ^true
- requestFocusIn - This shoud return a boolean. Before the KeyboardProcessor will activate your widget as the currently focused widget, it must answer true to this message. This is used, in the current framework for example, to do user entry validation.
requestFocusIn ^true
- requestFocusOut - This shoud return a boolean. Before the KeyboardProcessor will deactivate your widget (making it no longer the currently focused widget) it must return true to this (if it returns false, your widget retains focus). This can be used, for example to complete user edits, or validation.
requestFocusOut ^true
- activate - This is sent to your widget, after you've negotiated the requestFocusIn/requestFocusOut/desiresFocus dance. It's where you make things happen as a result of focusing your widget. If your widget draws differently when it's "focused", you may want to invalidate here.
activate self invalidate
- deactivate - This is sent to your widget, after you've negotiated the requestFocusIn/requestFocusOut/desiresFocus dance. It's where you make things happen as a result of defocusing your widget. If your widget draws differently when it's "focused", you may want to invalidate here.
deactivate self invalidate
- hasControl - This is my least favorite of the API, because it looks like a testing method. You can couple it with activate if you like, since they are similar. But it's not sent at the same time as activate it is. It is sent when the window containing your widget gets focus, and your widget is the focused widget in the window. So it's very good for toggling indication of focus that only shows up whether the window is focused or not.
hasControl self header invalidate
theWindow keyboardProcessor addKeyboardReceiver: myViewThis last requirement in particular I consider the most onerous, and we hope to make this more "snappable" real soon. You shouldn't have to worry about registering your widgets with the window (and subsequently unregistering them when cleanup is required).