Version 14 (modified by 2 years ago) ( diff ) | ,
---|
MVC
Model View Controller, or MVC in short, is a software design pattern used to develop GUIs.
The details of the approach are explained well on the MVC wiki page. In short,
- A model contains the data that the user is adjusting.
- A view shows the data in a GUI to the user (eg, graph, text field, slider position)
- A controller allows the user to change the data (eg dragging sliders, entering text or numbers, clicking check boxes)
A view and controller can, and often are, merged into a single component. For example a slider shows the current value, but the user can also drag the slider; or a checkbox shows the current setting (on or off) while the user can simply click on it to toggle the value.
Why a toolbox?
This toolbox is to help users develop an MVC based GUI. The tools built into Java lack some functionality: while Java provides a ListModel and a TableModel, it lacks other models such as a StringModel.
The Model
The toolbox has a hierarchy of Models. This section describes the various standard models.
Model and Compound Models.
At the root is the Model interface. A model basically is an object, which implicitly contains some values that can be adjusted. The interface only specifies that the object broadcasts Event objects whenever something changes in the model. A Model is called a Compound Model if it contains sub-models. For instance a Person compound model may contain a StringModel containing the name, and a NumberModel containing the age.
Event object
All Event objects are objects containing details of a change that happened in a model. It contains a reference to the source Model of the event, and possibly a child event if the change happened on some sub-model. There are four types of changes to a model:
- Changed: a sub-model of the model changed
- Added: a model (eg a list) just got extended by addition of a new submodel
- Removed: a model (eg a list) just got shrinked by removal of a submodel
- Selected: a model (eg in a list) just got selected
The notification mechanism
As mentioned the Model notifies all listeners when something in the model changes. The convention we try to stick to (but can not enforce) is that IllegalStateException
is thrown whenever the change in the model can not be accepted. This works as follows
- When setValue (or equivalent functions) receive a value that is unacceptable, eg because it's out of range, or because some parent model has an issue with it, an IllegalArgumentException is thrown immediately
- In the case of a compound object, the problem may not arise immediately but only indirectly. For instance suppose that Parsons with a name starting with "A" can not be older than 100 years. This check can not be done on the submodels containing the name or the age, but has to be done on the Person level. The Person model has to listen to changes to name and age, and this listener will throw if there is a conflict. This throw will then end up in the event notifier of the name or age model where the offending change will have to be reverted. Therefore the event notification in general is of the type ThrowingListenable and changes need to be reverted in case of conflicts. The notification mechanism generally will look like this
public void setValue(newval) oldval=this.val1; this.val1=newval; try { check(); notifyListeners(); } catch (IllegalStateException e) { log(e.getMessage()); this.val1=oldval; notifyListeners(); } }
Also note that any issues are pushed into the log system. This allows the GUI implementation to show any logged issues in a proper way to the user.
Basic Models
- BasicModel is a generic implementation of Model ment to store primitive objects eg String or numbers. It introduces a getValue and setValue function. There also is a check() function. The intention of the check() function is that a Model throws an exception if the value passed into setValue does not meet additional requirements.
- StringModel is a BasicModel containing a String
- NumberModel is a BasicModel containing a Number. Actually it contains a BigDecimal, which allows arbitrary precision numbers to be entered and manipulated without loss of precision or rounding.
- RestrictedNumberModel is a NumberModel with a minimum and maximum value.
ListModel
ListModel contains a list of Models and thus is a compound model. Elements can be added and removed to the ListModel using add and remove. Individual submodels can be fetched with get(). Properties can be checked with getSize() and contains().
MapModel
The MapModel contains a Map, or dictionary, of values. Both key and value are a Model so this is a compound model. That key and value are a Model implies that both key and value can be manipulated directly, without calling the MapModel directly. All implementations of MapModel will listen to such changes and notify the change anyway. MapModel provides a getValue(key) and a put(key,value) function. MapModels have a getKeys function that returns a ListModel of the keys in the map. Any changes to this listmodel must be reflected immediately into the map. Removing keys generally is easy, but when a key is added the MapModel must also insert the proper value. This may be easy or complex, up to the point where a popup may be needed to ask more information from the user.
DefaultMapModel is the basic implementation of MapModel. It is implemented using a LinkedHashMap, which helps keeping a fixed order in the elements. This fixed order is important to ensure the display of the elements in the GUI remains fixed. It also allows setting a minimum number of elements in the map
MapFromKeys is an implementation of MapModel that generates a Map from a ListModel. The ListModel contains the Keys of the Map. Note that ListModel can change at any moment, and MapFromKeys will always adapt immediately to meet the change. MapFromKeys ensures that all keys always have a value. If needed, the create() function is called to automatically generate a new value for a key. When a key is removed and then later re-added, MapFromKeys may remember the old value, if isRetainValues is set. This mechanism allows new maps to be generated from changing lists or maps.
SelectionModel
A SelectionModel adds selection to a Model, by adding setSelection(), getSelection() and getListModel(). The primary implementation is the DefaultSelectionModel. DefaultSelectionModel takes a ListModel and just stores the selection separately. When a selected list item is removed, the selection is cleared.
TypedModel and RealType
The RealType refers to the actual object contained in the model. This is especially relevant for Compound Models, such as the Person model described above. The getCurrentValue function now returns a Person. A TypedModel extends a Model by adding a RealType and the functions getCurrentValue and setCurrentValue to get/set the RealType contained by the model. The reason that the RealType is not included in Model is because the Models provided in this toolbox, and probably part of the Models developed by the GUI developer, must remain abstract and can not be linked to for instance a Person object already.
Panels
The panels model offers GUI components, aka Widgets, for use with the models. Currently there are the following panels:
ComboBox | allows to make selections in SelectionModel |
MapPanel | showing a MapModel as table with 2 columns. The left column shows the keys, the right column shows the values. All fields are editable. Editors for the models in the MapModel are generated using the PanelFactory. MapPanel is therefore limited to built-in models |