Pattern: Basic MVP Component


Context

When creating a Model-View-Presenter triad, you will have decided whether the component can be built from a collaboration of other components or whether it is elemental in nature. This pattern discusses how to create an elemental or Basic component once the underlying domain model class has been designed.

Diagrammatic representation of a basic component triad

You will already have created a model class. We assume here that there is no existing presenter and view combination suitable for providing the user interface for this model. We also assume that you cannot, or do not, want to create a composite component to handle the model.

Solution

Since there is no existing presenter class to handle your model's user interface you must create one.

Create the presenter class

You will use the New Class pattern to create a new presenter class. First of all, you must decide where this is to be subclassed from. All presenters inherit from Presenter in the class hierarchy. Follow these rules to decide on a suitable superclass.

  1. Decide if your model is to be mutable or not. A mutable object is one whose internal state can be changed using accessor methods. An immutable object cannot be changed in this way and a new object will always be created to replace an old one whenever a change is required. For example, a Point is mutable whereas an Integer is immutable. For immutable models you should descend from somewhere below ValuePresenter. In such cases your model will be handled as a ValueModel, which usually means that by default it will be wrapped as a ValueHolder.
  2. Otherwise, if your model is a list and supports the ListModel protocol then descend from ListPresenter.
  3. If your model is a tree and supports the TreeModelAbstract protocol then descend from TreePresenter.

    Tip: whenever possible you should try to descend from ValuePresenter. Since the protocol for a ValueModel is inherently simple (#value, #value:, #valueChanged) you again a great deal more flexibility in the way views can be plugged onto your component if this type of model is used.

Required presenter methods

#defaultModel -- Once you have created a presenter class you should add a #defaultModel class method to answer an object to use as the default model when an instance of this presenter component is created. This is normally a suitably initialized instance of your model class although, if you have created a ValuePresenter subclass you should wrap this in a ValueHolder by sending #asValue to the model object.

#defaultView -- A presenter must also support a #defaultView class method to answer the name of a resource that will yield a default view to be used when a instance of the presenter is created. You may choose to delay writing this method until after you have created a suitable view class and registered a named instance with the Resource Manager.

Obviously, you must add additional methods to the presenter class to perform manipulations of the model as required by the user. It's probably make sense to add these methods later once you have had a first pass at designing a view class and testing the entire MVP triad together.

Designing the view class

It may well be that an existing view class can be attached to your newly created presenter. However, it is more likely that you will have to create a new view class to support the display of the model's data in an appropriate format and to pass user gestures to the presenter. Once again, you'll use New Class and follow these rules to determine a suitable parent class:

  1. If your view is going to be a type of pre-defined Windows control that is not yet supported by the base Dolphin image then it should be descended beneath ControlView. Furthermore, if you are linking the view to a subclass of ValuePresenter then descend beneath ValueConvertingControlView. In this latter case, such views also support the concept of a type converter object that is able to convert from types of value held by the model to a type of object expected by the view. This just adds flexibility to the way that the view may be associated with different models. See the TypeConverter hierarchy for more details.
  2. If you expect your view class to contain child windows (sub-views) then descend from ContainerView.
  3. Otherwise, you should descend directly from class View. In the end, this is the most likely scenario.

Required view methods

A view is primarily responsible for displaying its model's data and registering with the model to receive change notifications so that appropriate portions of the display can be re-drawn when model is updated.

#onPaintRequired: -- override this method for all views that do not descend from ControlView. This should access the model data and render it to a Canvas onto the view's window as required. Note that, ControlViews, since they are Windows controls, are already expected to provide their own internal painting code so it is not usually appropriate to override this yourself.

#model: -- override this method to register with any required change notifications of the supplied model. Check through the superclass chain for your view to see what notifications are already intercepted by default.

Registering an instance of a view with the Resource Manager

A presenter can only connect to a view instance that has been created and a registered with the Resource Manager. Once you have created a view class you must install an instance of it as a resource. You do this by sending #makeResource:inClass: to the view class. In general, you will install a resource into the class of presenter that is likely to use it. A typical use of this method might be:

ScribbleView makeResource: 'Basic scribble view' inClass: Scribble

The name given to the resource here is that which must be answered by the presenter's #defaultView class method. Now is the time to update this if you have not already done so.

Once you have installed the view into the Resource Manager you may edit it in the View Composer (to set its default attributes such as size, font etc.) simply by double-clicking on its entry in the Resource Browser.

Examples

Example A: A drawing/scribble component has a mutable model since the contents of the drawing can be changed by adding or removing primitive operations. Therefore it should be descended from Presenter rather than ValuePresenter. In fact, you may well have chosen to make the model a ListModel in which case the Scribble presenter class should be beneath ListPresenter. In this case the inherited ListModel>>defaultModel method is probably entirely suitable. You should create ScribbleView as a subclass of View and override #onPaintRequired: to draw the representation of the model in the view's window. Since the drawing can be updated by adding or removing primitives from the ListModel then the view should register with the list's #item:addedAtIndex: and #itemRemovedAtIndex: notifications to redraw the appropriate portions of the window. A minimum implementation might be for the view to cause a complete repaint by sending itself an #invalidate message.

Example B: A text editing component using a String as a basic model could in truth be mutable. However, bearing in mind that descending from ValuePresenter and wrapping the String as a ValueModel is more flexible, this latter strategy is to be preferred. Hence, TextPresenter appears as a subclass of ValuePresenter and has as its default model:

^String new asValue

The TextEdit view class uses the standard Windows 'EDIT' control. It is descended from ValueConvertingControlView since it is also associated with a ValueModel. Because this is a Windows control there is no need to provide a specific #onPaintRequired: handler.

Known Uses

There are many classes in the standard development image for basic MVP components. These are used by the development tools and to provide a starter set of components for your own applications. Typical examples are: ListPresenter, ClassListPresenter, TextPresenter, DatePresenter etc.

Related Patterns