a simple mvc pattern for use with gtk#
I'm going to illustrate a simple model-view-controller (mvc) pattern that I've discovered after much trial and error. Also, there is nothing specific to gtk-sharp about it, you could use it for any UI toolkit.
The Problem
According to Wikipedia, the MVC pattern "isolates business logic from user interface considerations". If you've ever written a GUI application, you probably understand the need for it. For example, take my mapping application:
Note: I am using a mapping application as my example, but it could be anything.
The application is split into portions all interacting with an underlying map:
- The legend - adds/removes items from legend list as they are added/removed from map
- The map view - draws layers at given extents as defined by the map
- The scale - the scale (map units/pixels) updates as the underlying map scale changes
These widgets interact with each other. For example, if I remove a layer via the legend, the map must redraw without that layer.
The first instinct is to manage this interaction within the widgets, but what happens when you add a new widget that interacts? It's just not manageable. In the MVC pattern, our widget is the view. It's only job is to display data and allow the user to interact with it. Next, we try to unload it onto our underlying data object (the map in this example):
The underlying data object
The underlying data object is what your application is about. Mine is a mapping application, but it could be a financial spreadsheet with a List<IAccountTransaction> property or a blog viewer with methods for getting posts based on date/tag, and so forth...
My map class has properties that affect how the app must work:
get {
return layers;
}
set {
layers = value;
}
}
public int Width {
get {
return width;
}
set {
width = value;
}
}
....
These properties can be changed by the widgets in my app and other widgets need to know about the changes. Our next temptation is to manage this interaction in our data objects:
get {
return width;
}
set {
width = value;
// Bad!
OnWidthChanged();
}
}
Here, the OnWidthChanged() method would fire off an event that the widgets would attach to and update themselves on. The widget could also call the Map properties itself (e.g. to a add a layer). This sort of works, but again the problem is that it becomes quickly unmanageable.
Our data object (Map) becomes cluttered and obtuse because it is trying to do two things: be our data object and manage UI interaction.
The solution: A controller class
In the MVC pattern, our Map class is the model, so we need a third component, the controller. The controller will manage interaction between the view(s) and the model. My class, MapController works on a single map:
{
this.map = map;
}
And provides events for widgets to attach to:
public event EventHandler<LayerEventArgs> LayerAdded;
public event EventHandler<LayerEventArgs> LayerSymbologyChanged;
public event EventHandler ExtentsChanged;
public event EventHandler<MapRenderRequestArgs> MapRenderRequested;
...
And public methods for widgets to call that alter the model and notify other views:
{
map.Layers.Remove(layer);
OnLayerRemoved(layer);
}
You'll need to provide hooks for as much of the underlying model you want the UI to have access to.
Binding the views to the controller
Now, we just need to integrate this into the widget. For this, I created an interface:
{
void Bind(MapController controller);
void Unbind();
}
Widgets that want to interact with the map must implement this. For example, the legend:
{
...
public void Bind (MapController controller)
{
mapController = controller;
mapController.LayerAdded += LayerAddedHandler;
mapController.LayerRemoved += LayerRemovedHandler;
}
...
Unbind() would detach the handlers and do any cleanup (e.g. clear our the legend). Also, Bind/Unbind should recursively bind/unbind any child widgets, for example, my map window:
{
mapController = controller;
// bind our widgets
mapWidget.Bind(mapController);
tv.Bind(mapController);
mapController.ExtentsChanged += ExtentsChangedHandler;
}
Note: you could also automatically bind child widgets. For example, in a container, iterate through Widget.Children and bind any that implement IMapControllable.
Finally, at the root window of your application you would create the model and the controller:
And that's it. Everything is hooked up.
Flaws?
One flaw in my implementation is some of my widgets require full access to the model (Map) which I expose as a property on the controller, so they could potentially circumvent the controller by performing actions directly on the model. Ideally, you would want to avoid this or create a read-only version of your object to pass to views.
Unit Testing
Unit testing the controller is easy with this setup, you'd need a dummy IMapControllable:
public class MapControllerTests
{
class DummyMapControllable : IMapControllable
{
public MapController c;
public bool LayerRemoved = false;
public void Bind (MapController controller)
{
c = controller;
c.LayerRemoved += delegate { LayerRemoved = true; };
}
public void Unbind ()
{
c = null;
}
}
DummyMapControllable view = new DummyMapControllable();
Map m;
[TestFixtureSetUp]
public void TestSetUp()
{
m = new Map();
MapController controller = new MapController(m);
view.Bind(controller);
}
[Test()]
public void TestRemoveLayer()
{
Layer l = new Layer();
m.Layers.Add(l);
Assert.IsFalse(view.LayerRemoved);
view.c.RemoveLayer(l);
Assert.IsTrue(view.LayerRemoved);
}
}
This example tests MapController.RemoveLayer. It makes sure that it will trigger the necessary event. Note that the code that the controller calls into the model should be tested separately within the model's unit tests (in the business layer).
Summary
We now have an extensible, modular, tested user interface. An important sign that you are doing it correctly is that your widgets have no public methods except for Bind/Unbind (except for those that are inherited). This means that interaction is only occurring directly through the user interface or through the controller.
