- 9.1 The Core: Model-View Separation
- 9.2 The Model-View-Controller Pattern
- 9.3 The JFace Layer
- 9.4 The MVC Pattern at the Application Level
- 9.5 Undo/Redo
- 9.6 Wrapping Up
9.2 The Model-View-Controller Pattern
The MODEL-VIEW-CONTROLLER pattern (MVC) has proven a tremendous success in many different areas of user interfaces, starting from the original SmallTalk toolkit, through all major players such as Qt, GTK, SWT, 146 Swing, and MFC, right to web application frameworks such as Ruby on Rails and ASP.MVC. Naturally, the different areas have produced different variants that suit their specific needs. Nevertheless, the fundamental concept remains the same. We will study here the classical version, which will 59 also clarify the workings of the variants. We will use a minimal example to illustrate the conceptual details of the pattern clearly without swamping the discussion with unnecessary technical complications. A more extended example will be given in the MiniXcel application. Also, we start out with 9.4 the classical separation of view and controller, even if most practical implementations unify these roles. Understanding the separate responsibilities of 9.2.8 view and controller separately first will later help to create clearer structures.
9.2.1 The Basic Pattern
The structure of the MVC pattern is shown in Fig. 9.3. In essence, the pattern reflects the model-view separation: The business logic is kept separate 9.1 from the user interface code, and the logic collaborates with the interface only through generic change notifications in the OBSERVER. The pattern 2.1 adds a finer subdivision in the interface layer: The view is responsible for rendering the application data on the screen, while the controller contains the logic for reacting to user input.
Figure 9.3 The Basic Model-View-Controller Pattern
The benefit of this additional split is mainly a stricter separation of concerns. We have seen in the discussion of the MEDIATOR that the event-listeners 7.7 attached to widgets can quickly become complex in themselves. Moving this code into a self-contained object will keep the code of the view more focused on the visual presentation itself. Although many practical implementations reunite the two roles in the DOCUMENT-VIEW variant, 9.2.8 is useful to consider them separately first, since this will lead to a clearer structure within the view component of this later development.
In summary, the three roles of the pattern then perform these tasks:
- 9.1 The model maintains the application’s data structures and algorithms, which constitute its business logic. The model is the valuable and stable core of the product; it is built to last through revisions and ports 7.11 to different window systems. It builds on precise contracts and is thoroughly unit-tested.
- The view renders the current state of the application data onto the screen. It accesses the model to retrieve the data, and registers as an observer to be notified about any changes and to keep the display 7.1 up-to-date. By technical necessity, it also receives all user input as events and passes those events on to the controller.
- The controller interprets the user input events as triggers to perform operations and modifications on the model. It contains the logic for 1.8.1 handling the events. In this role, it is a typical decision maker: It decides what needs to be done, but delegates the actual execution to others. In the basic pattern, this means calling the model’s methods.
To see the pattern in action, we implement a tiny widget that enables a single integer value to be incremented and decremented by clicking on 7.5 different areas (Fig. 9.4). Rather than building a compound widget, we implement this from scratch to show all of the details.
Figure 9.4 Minimal MVC Example
The model maintains the application data and supports observers.
9.1 Following the earlier advice, we start with the model. Its “functionality” is to maintain a single integer value. To serve as a model in the pattern, the object also implements the OBSERVER pattern. The crucial point to be noted is that the model is in no way adapted to the intended presentation on the screen. In particular, the observers are merely notified that the content has changed (line 21); there is no indication that this notification will trigger a screen update later on.
celledit.mvc.IntCell
1 public class IntCell { 2 private int content; 3 private EventListenerList listeners = new EventListenerList(); 4 public void addCellListener(CellListener l) { 5 ... 6 } 7 public void removeCellListener(CellListener l) { 8 ... 9 } 10 public int get() { 11 return content; 12 } 13 public void set(int cnt) { 14 int old = content; 15 this.content = cnt; 16 fireCellChanged(old); 17 } 18 protected void fireCellChanged(int old) { 19 for (CellListener l : listeners.getListeners( 20 CellListener.class)) 21 l.cellChanged(this, old, content); 22 } 23 }
The view displays the data on the screen.
The view in the pattern must paint on the screen, so it derives from Canvas. 7.8 It keeps references to the current model and controller, as well as the (larger) 7.4.1 font used for painting and the computed preferred size. 7.1
celledit.mvc.View
public class View extends Canvas { private IntCell model; private Controller controller; private Font fnt; private Point sizeCache; ... }
The main task of the view is to render the application data on the 7.8 screen. The following excerpt from the painting method gives the crucial point: Line 3 gets the current value from the model and transforms it into a string to be drawn on the screen in line 7. The remaining code serves to center the string in the widget (bounds is the area available for painting).
celledit.mvc.View
1 private void paintControl(PaintEvent e) { 2 ... paint red and green fields 3 String text = Integer.toString(model.get()); 4 Point sz = g.textExtent(text); 5 int x = bounds.width / 2 - sz.x / 2; 6 int y = bounds.height / 2 - sz.y / 2; 7 g.drawString(text, x, y); 8 } 9
The view keeps the display up-to-date by observing the model.
To keep the display up-to-date, the view must observe the model. Whenever the model changes, the view observes the new model.
celledit.mvc.View
public void setModel(IntCell c) { if (this.model != null) this.model.removeCellListener(modelListener); this.model = c; if (this.model != null) this.model.addCellListener(modelListener); }
7.8 The modelListener merely requests a complete repainting of the widget. In many scenarios, this is too inefficient for production use, so that 9.4.3 incremental repainting must be implemented. To demonstrate the pattern, the simple choice is sufficient.
celledit.mvc.View
private CellListener modelListener = new CellListener() { public void cellChanged(IntCell cell, int oldVal, int newVal) { redraw(); } };
The view forwards user input to the controller.
Finally, the view must forward the events to the controller. This is usually achieved by registering the controller as an event-listener. For the current example, we delegate the actual registration to the controller itself to demonstrate an exchange of the controller later on. 9.2.7
celledit.mvc.View.setController
public void setController(Controller c) { if (controller != null) controller.detach(this); controller = c; if (controller != null) controller.attach(this); }
Having finished with the model and the view, we have set up the main axis of Fig. 9.3: The display on the screen will always reflect the current data, independent of how that data will be manipulated. We will now add this last aspect by implementing the controller.
The controller receives all relevant user input.
The controller must receive all user input relevant to the expected reactions. Since the input is technically sent to the view, the controller registers itself as a listener on the view. In the current example, it becomes a mouse-listener to receive the clicks that will trigger the increment and decrement operations. (The super call merely remembers the view in a field view.)
celledit.mvc.MouseController.attach
public void attach(View view) { super.attach(view); this.view.addMouseListener(this); this.view.addMouseTrackListener(this); this.view.addMouseMoveListener(this); }
The controller interprets the events as operations on the model.
The summary of tasks given earlier states that the purpose of the controller is to translate raw input events into operations on the model. The implementation can be seen in the callback methods for mouse clicks. The controller accesses the model to be operated on (lines 2–3) and checks which 9.2.3 area the click actually occurred in (lines 4 and 7). Based on this information, it decides whether the model value should be incremented or decremented (lines 6 and 8). As a detail, the controller decides not to decrement the value if it has already reached 0.
celledit.mvc.MouseController.mouseUp
1 public void mouseUp(MouseEvent e) { 2 if (view.getModel() != null) { 3 IntCell m = view.getModel(); 4 if (view.isInDecrementArea(new Point(e.x, e.y)) && 5 m.get() > 0) 6 m.set(m.get() - 1); 7 else if (view.isInIncrementArea(new Point(e.x, e.y))) 8 m.set(m.get() + 1); 9 } 10 }
The pattern processes input through view, controller, and model.
The overall goal of the MODEL-VIEW-CONTROLLER pattern can also be seen by tracing the user input through the different roles, until an actual screen update occurs.
- The view receives the input and hands it to the controller.
- The controller decides which action to take on the model.
- The model performs the invoked operation and sends the resulting changes to the view, as one of possibly several observers.
- 9.4.3 The view interprets the model changes and decides which parts of the screen need to be redrawn.
- The view refetches the relevant data and paints it on the screen.
This sequence of steps highlights the contributions of the different objects. It also points out that each of them can influence the final outcome: The view will contribute the visual appearance; the controller implements the reaction, since the view simply forwards events; and the model implements the functionality, but does not see the input events.
The central point of model-view separation is seen in steps 2 and 3. First, the controller alone is responsible for interpreting the input events; the model is not aware of the real causes of the invoked operations. Second, the model is not aware of the precise view class, or that there is a user interface at all; it merely supports the OBSERVER pattern.
9.2.2 Benefits of the Model-View-Controller Pattern
The MODEL-VIEW-CONTROLLER pattern is, in fact, rather complex and requires some extra implementation effort, compared to the naive solution of implementing the application’s functionality directly in event-listeners attached to the widgets. The investment into the extra structure and indirections introduced by the pattern must therefore be justified.
The user interface remains flexible.
The most important benefit of the pattern derives from its ability to keep the user interface flexible. Because the application’s functionality stays safe and sound in the model component and does not depend on the user interface in any way, it will remain valid if the interface changes. This can and will happen surprisingly often over the software’s lifetime.
The first reason for changing the user interface is the user. The central goal of a user interface is to support the users’ workflows effectively. As 229 these workflows change or the users develop new preferences, the interface should ideally be adapted to match them. Also, different user groups may have different requirements, and new views may need to be developed as these requirements emerge. The MVC pattern confines such changes to the actual interface, unless the new workflows also require new computations and operations.
The second reason for changes relates to the underlying window system. When APIs change or new widgets or interaction devices are developed, the user interface must exploit them for the users’ benefit. Since these aspects are usually not related to the functionality in any way, the MVC keeps the application’s core stable.
Finally, it may be desirable to port the application to an entirely different platform. Here, the problem lies mostly in the user interface. In the best case, an analogous set of widgets will be available: Whether you access Windows, MacOS, GTK, or Qt, their widgets offer basically very similar services and events. Nevertheless, the user interface must usually be redeveloped from scratch. The MVC pattern ensures that the valuable core of the application, its functionality, will continue to work in the new environment, since this core uses only standard services such as file or network access, for which cross-platform APIs are available or where the platform differences can be hidden behind simple adapters. 2.4.1
Multiple, synchronized views can better support the users’ workflows.
Modern IDEs such as Eclipse give us a good grasp on our source code. For example, while we work on the source in a text editor, we see an outline of its structure on the side. When we rename a method in one of the two windows, the other window reflects the change immediately. The reason is simply that both windows are, possibly through an indirection of the Java Model, views for the same text document, which fulfills the role of the view component in the MVC pattern. Similarly, Eclipse’s compiler reports an error only once by attaching an IMarker object to the file. The marker is reflected in the editor, the problems view, and as a small icon in the package explorer and project navigator.
The MODEL-VIEW-CONTROLLER pattern enables such synchronized views on the application’s data structure because views observe the model and are informed about its current state regardless of why changes have occurred.
The display remains up-to-date with the internal state.
At a somewhat more basic level, users will trust an application only if they are never surprised by its behavior. One common source of surprises is inconsistency between the internal data structures and the displayed data. The MVC pattern eliminates this chance completely and ensures that the users always base their actions and decisions on the most up-to-date information about the internal structures.
The application’s functionality remains testable.
5.4 The single most important technique for making a system reliable and keeping it stable under change is testing. By making the functional core, the model, independent of a user interface, its operations can also be exercised in a testing fixture (see Fig. 5.1 on page 246) and its resulting state can be examined by simple assertions in unit tests. Testing the user interface, 5.3.5 in contrast, is much more complex. Since the user interface itself tends to change very often, the effort of adapting the existing test cases and creating new ones will be considerable. The functional core, in contrast, is built to remain stable, so that the investment of testing will pay off easily.
Model-view separation enables protection of the system’s core.
The stability of an application’s functionality relies heavily on precise contracts. 4.1 Within this reasoning framework, each method trusts its callers to fulfill the stated pre-condition—that is, to pass only legal arguments and 4.5 to call the method only in legal object states. The non-redundancy principle condenses the idea of trust into the development practice of never 4.6 1.5.2 checking pre-conditions. At the system boundary, in contrast, the code can never trust the incoming data and requests. Methods must be written to be robust, and to check whether they really do apply.
Model-view separation offers the benefits of localizing these necessary checks in the user interface component and maintaining the functional core in the clean and lean style enabled by the non-redundancy principle.
9.2.3 Crucial Design and Implementation Constraints
2.1.2 As with the OBSERVER pattern, the concrete implementation of the MODEL-VIEW-CONTROLLER pattern must observe a few constraints to obtain the 9.2.2 expected benefits. We list here those aspects that we have found in teaching to make the difference between the code of novices and that of professionals.
Do not tailor the OBSERVER pattern to a specific view.
The first aspect is the definition of the Observer interface for the model. Especially when dealing with complex models and the necessity of incremental 9.4.3 screen updates, there is always the temptation to “tweak” the change notifications a bit to simplify the logic that determines which parts of the screen need to be updated. Certainly, one should use the “push” variant of the Observer pattern; that is, the change notifications should be very detailed to 2.1.3 enable any view to work efficiently regardless of its possible complexity.
When targeting the messages at specific views, however, one endangers the ability to add a new view or to change the existing one, or to port the application to an entirely different platform. Suppose, for instance, that the model manages a list of objects with some properties. It should then send a change message containing a description of the change. However, it should not use a message updateTableRow() simply because the current view is a Table widget. A better choice is a message changedData(), which reflects the change instead of the expected reaction. If the view displays the properties in a specific order, the model must not send messages update Table(int row, int col), but rather changedData(DataObject obj, String property). Even if this means that the view must map objects to rows and the property names to column indices, it increases the likelihood that the view can change independently of the model.
The controller never notifies the view about triggered operations.
A second shortcut that one may be tempted to take is to let the controller notify the view directly about any changes it has performed on the model, rather than going through the indirection via the model. First, this shortcut is marginally more efficient at runtime. What is particularly attractive, however, is that it saves the implementation of the general OBSERVER pattern 2.1.4 tern in the model and the perhaps complex logic for translating changes to screen updates in the view.
However, the shortcut really destroys the core of the pattern, and nearly all of its benefits. One can no longer have multiple synchronized views. Also, the information on the screen may no longer be up-to-date if the controller neglects internal side effects and dependencies of the model. Finally, the 9.4.2 logic for the updates must be duplicated in ports and variations of the user interface.
The controller delegates decisions about the visual appearance to the view.
A comparatively minor point concerns the relationship between the view and the controller. If these roles are implemented as different objects at any 9.2.8 point, then one should also strive for a strict separation of concerns—for instance, to keep the controller exchangeable. 9.2.7
One notable aspect is the possible assumptions about the visual appearance. The controller often receives events that relate back to that visual appearance. For instance, a mouse click happens at a particular point on the screen, and the visual element at this point must determine the correct 12.1.2 reaction. If the controller makes any assumptions about this visual element, it is tied to the specific implementation of the view. If several controllers exist, then it becomes virtually impossible to change even simple things such as the font size and spacing, since several controllers would have to change as well.
9.2.1 In the following tiny example, we have therefore made the controller ask the model whether the click event e occurred in one of the designated “active” areas. The controller now assumes the existence of these areas, but it does not know anything about their location and shape. That knowledge is encapsulated in the view and can be adapted at any time.
celledit.mvc.MouseController.mouseUp
if (view.isInDecrementArea(new Point(e.x, e.y)) && m.get() > 0) m.set(m.get() - 1); else if (view.isInIncrementArea(new Point(e.x, e.y))) m.set(m.get() + 1);
9.2.8 Even in the common DOCUMENT-VIEW variant of the MVC, where view and controller are implemented together in one object, it is still useful to obey the guideline by separating the concerns into different methods of the object.
The controller shields the model from the user input.
1.5.2 4.6 The user interface is, of course, one of the system’s boundaries. Accordingly, all user input must be treated with suspicion: Has the user really entered valid data? Has the user clicked a button only when it makes sense? Does the selected file have the expected format?
Many of these questions are best handled in the controller, because it is the controller that receives the user input and decides which model 7.11 operations need to be called in response. Since the model is built according to 4.5 the principles of design by contract, it does not check any stated preconditions. It is the controller’s task to ensure that only valid method calls are made.
9.2.4 Common Misconceptions
The MODEL-VIEW-CONTROLLER pattern is rather complex, so it is not surprising that a few misunderstandings arise when first thinking it through. We have found in teaching that some misunderstandings tend to crop up repeatedly. They seem to arise mostly from the correct impression that 12.2 the MVC is all about exchangeability and flexibility. However, one has to be careful about what really is exchangeable in the end and must not conclude that “all components can be exchanged and adapted to the users’ requirements.” We hope that highlighting the nonbenefits of the pattern in this section will enhance the understanding of the benefits that it does create.
Model-view separation is not a panacea.
The rather extensive mechanisms and logic necessary for establishing a proper model-view separation must always be seen as an investment. It is an investment that pays off quite quickly, even for medium-sized applications, but it is still an investment. The decision for or against using the MVC must therefore be based on a precise understanding of it benefits, so as to relate them to the application at hand. A small tool written for one project only will never need porting, for example, and if the developer is also its only user, there is little chance of having to change the user interface. A general understanding that the MVC offers “everything that can be wished for” is not enough.
The model is not exchangeable and the view is not reusable.
The view and the controller necessarily target a specific model: They ask the model for data and draw exactly that data; the view registers as an observer and expects certain kinds of change messages; and the controller translates user gestures into specific operations offered by the model. As a result, the model cannot usually be exchanged for a different one; by switch of perspective, this means that the view is usually not reusable.
Of course, it is still possible to implement generic widgets that access the model only through predefined interfaces. For instance, a table on the screen has rows, and the data in each row provides strings for each column. Both JFace and Swing provide excellent examples of generic and reusable 9.3.1 80 tables. However, this is an exercise in library or framework design. To build a concrete user interface, one has to supply adapters that link the generic mechanisms to the specific application model, and one has to implement listeners for generic 2.4.1 table events that target the specific available model operations. In this perspective, the generic table is only a building block, not the complete user interface in the sense of the MVC.
The controller is usually neither exchangeable nor reusable.
The controller interprets user gestures, such as mouse moves, mouse clicks, and keyboard input. These gestures have a proper meaning, and hence a reliable translation to model operations, only with respect to the concrete visual appearance of the view. It is therefore usually not possible to reuse a controller on a different view. Exchanging the controller is possible, but 9.2.7 only within the confines of the event sources offered by the view.
9.2.5 Behavior at the User Interface Level
Effective user interfaces allow the user to invoke common operations by small gestures. For example, moving a rectangle in a drawing tool takes a mouse click to select the rectangle and a drag gesture to move it. Since many similarly small gestures have similarly small but quite different effects, the application must provide feedback so that the user can anticipate the reaction. For instance, when selecting a rectangle, it acquires drag handles—that is, a visual frame that indicates moving and resizing gestures will now influence this object.
Implement user feedback without participation of the model.
The important point to realize is that feedback is solely a user interface behavior: Different platforms offer different mechanisms, and different users will expect different behavior. The model does not get involved until the user has actually triggered an operation.
9.2.1 Suppose, for instance, that we wish to enhance the example widget with the feedback shown in Fig. 9.5. When the mouse cursor is inside the widget, a frame appears to indicate this fact (a versus b and c); furthermore, a slightly lighter hue indicates whether a click would increment or decrement the counter (b versus c), and which field is the current target of the click.
Figure 9.5 User-Interface Behavior: Mouse Feedback
Feedback is triggered by the controller.
The second aspect of feedback concerns the question of which role will actually decide which feedback needs to be shown. The answer here is clear: Because the controller will finally decide which operation is triggered on the model, it must also decide which feedback must be shown to apprise the user of this later behavior. It is similarly clear that the controller will decide on the feedback but will delegate the actual display to the view.
In the implementation of the example, the Controller tracks both the general mouse movements into and out of the widget, and the detailed movements inside the widget. The reaction to the mouseEnter and mouseExit events is straightforward: Just tell the view to draw the frame or to remove it. When the mouse leaves the widget, any target highlight must, of course, also be removed. The mouseMove proceeds in parallel to the 9.2.1 mouseUp method in the basic implementation: It checks which operation it would perform and sets the corresponding highlight.
celledit.mvc.MouseController
public void mouseEnter(MouseEvent e) { view.setInside(true); } public void mouseExit(MouseEvent e) { view.setInside(false); view.setTargetField(View.TARGET_NONE); } public void mouseMove(MouseEvent e) { if (view.isInDecrementArea(new Point(e.x, e.y))) view.setTargetField(View.TARGET_DECREMENT); else if (view.isInIncrementArea(new Point(e.x, e.y))) view.setTargetField(View.TARGET_INCREMENT); else view.setTargetField(View.TARGET_NONE); }
Feedback usually requires special state in the view.
In implementing the actual visual feedback within the View, we have to take into account one technical detail: Painting always occurs in a callback, 7.8 at some arbitrary point that the window system deems suitable. The view must be ready to draw both the data and the feedback at that point. We therefore introduce special state components in the view:
celledit.mvc.View
private boolean inside = false; public static final int TARGET_NONE = 0; public static final int TARGET_DECREMENT = 1; public static final int TARGET_INCREMENT = 2; private int targetField = TARGET_NONE;
The View publishes the new state, but only to its related classes, such as the Controller. The setter for the state stores the new value and invokes 7.8 redraw() to request a later painting operation. Since this is potentially expensive, one should always check whether the operation is necessary at all.
celledit.mvc.View
protected void setInside(boolean inside) { if (this.inside == inside) return; this.inside = inside; redraw(); }
The actual painting then merely checks the current feedback state at the right point and creates the visual appearance. Here is the example for highlighting the “decrement” field; the increment field and the “inside” indications are similar.
celledit.mvc.View
private void paintControl(PaintEvent e) { ... if (targetField == TARGET_DECREMENT) g.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED)); else g.setBackground(getDisplay().getSystemColor( SWT.COLOR_DARK_RED)); g.fillRectangle(bounds.x, bounds.y, bounds.width / 2, bounds.height); ... }
Separate view-level state from the application functionality.
The example of the feedback given here has introduced the necessity of state that only lives at the view level but does not concern the application’s core data structures. A plethora of similar examples comes to mind immediately: the selection in a text viewer or the selected row in a table; the folding and unfolding of nodes in a tree-structured display, such as SWT’s Tree; the currently selected tool in an image editor; the position of scrollbars in a list and the first row shown in consequence; the availability of buttons depending on previous choices; and many more.
In the end, the view-level state and the model-level state must be merged in one consistent user interface with predictable behavior. Internally, however, the two worlds must be kept separate: The one part of the state is thrown away, and the other must be stable when the interface changes; the one part is best tested manually, and the other must be rigorously unit-tested. Consequently, one must decide for each aspect of the overall state to which of the worlds it will belong.
The decision may seem rather obvious at first, but some cases might merit deeper discussions and sometimes one may have second thoughts about a decision. For instance, the GIMP image editor treats the selection as part of the model: You can undo and redo selection steps, and the selection 9.5 even gets saved to the .xcf files. The reason is, obviously, that in the image manipulation domain, selection is often a key operation, and several detailed selection steps must be carried out in sequence to achieve a desired result. Being able to undo and redo selection helps users to remedy mistakes in the process.
9.2.6 Controllers Observing the Model
In the basic MODEL-VIEW-CONTROLLER pattern, the view necessarily observes 9.2.1 the model, because it must translate any changes in the data to updates of the display. In many scenarios, the controller will also observe the model.
Controllers can observe the model to indicate availability of operations.
A typical example of this behavior is seen in menu items that get grayed out if an operation is not available. For instance, a text editor will gray out the “copy” and “cut” entries if there is currently no selection.
The controller decides on the availability of operations.
It might be tempting to integrate the feedback on available actions directly into the view. After all, the view already observes the model and it can just as well handle one more aspect while it is at work anyway. However, since the controller decides which operations it will invoke for which user input, it is also the controller which decides whether these operations are currently available.
Suppose, for instance, that we wish to gray out the decrement field if the current count is already 0. This requires an extension of both the View and the Controller classes: The view acquires a new bit-mask stating which of the fields need to be grayed out, and that information is used when choosing the background color in paintControl(). The controller observes the model and switches the “gray” flags of the fields according to the current model value.
Controllers must assume that others modify the model.
One possible pitfall that leads to nonprofessional code lies in the fact that the controller modifies the model itself and therefore seems to know precisely whether an operation causes some action to become unavailable. However, it should be noted that the MVC is built to support multiple synchronized views, and that other controllers may invoke model operations as well. Each controller that depends on the model’s state must therefore observe the model.
9.2.7 Pluggable Controllers
9.2.8 Even if, as we shall see shortly, the view and controller are often coupled so tightly that it is sensible to implement them in a single object, it is still 146 instructive to consider briefly the concept of making the controller of a view pluggable to implement new interactions with an existing graphical presentation. This flexibility can be achieved only after understanding precisely the division of responsibilities between view and controller.
So, let us implement a controller that enables the user to access the 9.2.1 number entry field from the introductory example (Fig. 9.4 on page 455) via the keyboard. The new KeyboardController waits for keyboard input and modifies the model accordingly. Since the view observes the model, the change will become visible to the user.
celledit.mvc.KeyboardController.keyReleased
public void keyReleased(KeyEvent e) { IntCell m = view.getModel(); switch (e.character) { case '+': m.set(m.get() + 1); break; case '-': if (m.get() > 0) m.set(m.get() - 1); break; } ... }
Keyboard input is different from mouse input in that it is not the current location of some cursor, but the keyboard focus of the window system (and SWT) that determines which widget will receive the events. The keyboard focus is essentially a pointer to that target widget, but it has interactions 7.6 with the window manager (because of modal dialogs) and the tab order of widgets in the window. It is therefore necessary to display feedback to the users so that they know which reaction to expect when they press a key. The new controller therefore registers as a FocusListener of the View.
celledit.mvc.KeyboardController.attach
public void attach(View view) { ... view.addFocusListener(this); }
The controller then uses the existing “inside” indication on the view for the actual feedback:
celledit.mvc.KeyboardController
public void focusGained(FocusEvent e) { view.setInside(true); } public void focusLost(FocusEvent e) { view.setInside(false); }
Another convention is that clicking on a widget with the mouse will give it the focus. This is, however, no more than a convention, and the widget itself has to request the focus when necessary. This reaction can be implemented directly. (Note that the actual indication that the focus has been obtained is shown indirectly, through the event-listener installed previously.)
celledit.mvc.KeyboardController.mouseUp
public void mouseUp(MouseEvent e) { view.setFocus(); }
Finally, it is also useful to give a visual indication, in the form of a short flash of the respective increment/decrement fields, when the user presses the “+” and the “-” keys. This, too, can be achieved with the existing feedback mechanisms. The keyReleased() event then resets the target field to “none.” The flash will therefore mirror precisely the user’s pressing of the respective key.
celledit.mvc.KeyboardController.keyPressed
public void keyPressed(KeyEvent e) { switch (e.character) { case '+': view.setTargetField(View.TARGET_INCREMENT); break; case '-': view.setTargetField(View.TARGET_DECREMENT); break; } }
The new controller emphasizes the division of logic between the view and the controller: The display and highlights remain with the view, and the controller decides what needs to be done in reaction to incoming user input. It is this division that has enabled us to reuse the existing highlight mechanisms for new purposes.
You might, of course, be suspicious of this reuse: Was it just coincidence 12.4 that the existing mechanisms worked out for the new controller? Reuse always requires anticipating the shape of possible application scenarios and keeping the supported ones lean at the cost of excluding others. In the current case, we would argue that the feedback mechanisms that the view provides match the user’s understanding of the widget: The user “activates” the widget by “zooming in,” either by the mouse or by the keyboard focus, and then “triggers” one of the increment and decrement areas. All of these interactions are then mirrored by the highlights.
Nevertheless, it must be said that views and controllers usually depend heavily on each other, so that exchanging the controller is rarely possible. 214 One example where it is enabled is found in the pluggable edit policies of the Graphical Editing Framework, which create a setup where reusable controller-like logic can be attached to various elements of the user interface in a flexible way.
9.2.8 The Document-View Variant
The view and controller in the MVC pattern are usually connected very tightly: The controller can request only those events that the view provides, and it can make use of only those feedback mechanisms that the view implements. Since it is therefore often not possible to use either the view or the controller without the other, one can go ahead and implement both roles in the same object. This leads to the DOCUMENT-VIEW pattern, where the 9.1 document contains the application logic and the view contains the entire user interface code. In this way, the interface code can share knowledge about the widget’s internals between the logic of the display and the event-listeners. This may facilitate coding and avoids having to design an API that enables the view and the controller classes to communicate.
9.2.1 Let us examine this idea through the simple example of incrementing and decrementing an integer value. We start from a technical perspective. 7.8 Since we need to implement a widget with custom painting, the overall structure is that of a Canvas with attached listeners. The drawing part is actually the same as in the previous implementation. Only the code for the event-listeners is integrated. In the simplest case, we wait for mouse 2.1.3 clicks. To avoid publishing this fact by making the View class implement MouseListener, we attach an anonymous listener that delegates to the outer class.
celledit.docview.View.View
addMouseListener(new MouseAdapter() { public void mouseUp(MouseEvent e) { handleMouseUp(e); } });
Keep the code for display and reaction loosely coupled.
On the first try, one is liable to take the freedom of “sharing knowledge” between display and event-listeners very literally. For instance, we know that paintComponent() draws the dividing line between the decrement and increment fields right in the middle of the widget’s screen space. The event-listener can therefore be written up like this:
celledit.docview.View
private void mouseUp1(MouseEvent e) { Rectangle area = getClientArea(); if (cell.get() > 0 && area.width / 2 <= e.x && e.x <= area.width && 0 <= e.y && e.y <= area.height) cell.set(cell.get() - 1); ... };
However, this is highly undesirable: It is not possible to change the visual appearance without going through the entire class and checking which code might be influenced. It is much better to introduce a private helper 1.4.5 method that decides whether a particular point is in the increment or decrement fields. Placing this helper near the paintComponent()—that is, splitting the class logically between display and reaction code—will greatly facilitate maintenance.
celledit.docview.View
private void handleMouseUp(MouseEvent e) { if (cell.get() > 0 && isInDecrementArea(new Point(e.x, e.y))) cell.set(cell.get() - 1); ... }; private boolean isInDecrementArea(Point p) { ... }
In the end, this implementation is very near the original division between view and controller. One crucial difference is that now the helper method is not an external API that may be accessed from the outside and must therefore be maintained, but rather a private, encapsulated detail that may be changed at any time without breaking other parts of the system.
With predefined widgets, access their API directly.
In many cases, the actual display consists of predefined widgets such as text fields or tables. These widgets already encapsulate all painting-related aspects so that it is not necessary to introduce helpers. The DOCUMENT-VIEW pattern then applies very directly, since listeners can get the content or the selection of widgets without further ado.