Model-view-controller is an old, old, old but very good idea. It encourages the separation of model, presentation and control from each other. It’s used in so many places I can’t name them but frameworks like: Struts and Ruby-On-Rails actually enforce it.
For a long time it seems to me that Microsoft has lagged behind in allowing us to use this idea. Their once flagship product, Visual Basic 6, makes it almost impossible to write good MVC code. First of all, in VB6 there is no real inheritance which makes writing good models difficult. Secondly, if those models should contain any items that generate events then those items can not be defined in a class module and must be made public. Sure you can simulate and work-around these things by various means but in the end you will just be fighting the language. And that is never good.
So it’s good to see VB.NET, or .NET 2.0 to be precise, not only has excellent object support but a mechanism that can be used for MVC is actually built into the language. Someone mentioned to me, before I started using .NET, that you could use .NET delegates for MVC. I nodded my head sagely and life continued unchanged for me. So when it came to actually needing to do MVC in .NET I thought I’d just Google it and that would be that. Several articles came back, but it seems that of the articles that actually worked when I tried them the descriptions about the application of MVC to .NET was more than lacking. In short the articles focused on the event mechanism and that’s all. Whilst this is an important part of the puzzle that’s definitely not all and so this is my attempt. Hopefully mine will be better 🙂
To begin with it’s worth mentioning that MVC has to be modified to fit into most modern GUI component frameworks. However, web-based frameworks actually do fit quite neatly into MVC and the approach requires no modification.
The reason that GUI frameworks are a problem is that today’s client GUIs are substantially different from the GUIs of yester-year and these days the framework or OS actually takes up the responsibility of some of the parts of the traditional MVC triad. Unfortunately this is where things often start to go wrong IMHO. People see the obvious benefits of MVC and try to incorporate it into their GUI applications. But as we already said MVC does not, as it stands, fit neatly into component based frameworks like Swing or Windows.Forms.
Some very clever people have thought a lot about this. If you think I’m crazy you should first check out a paper written by Taligent systems: MVP: Model-View-Presenter. If you still think I’m crazy you should read about ObjectArt’s experience with trying to fit MVC (a Smalltalk idea) into their Smalltalk system. If you still think I’m crazy I’ll go and see a psychiatrist.
So a good treatment of MVP is beyond the scope of this post. But I will break down the component pieces as applied to an example and that should probably serve to demonstrate how it could be done and what’s trying to be achieved.
Ok, so let’s have an example and get on with the show. The easiest to comprehend is probably going to be something like an order, which has order items. The presenter adds the items to the order model and the view is then responsibile for showing the order items when they update.
Model
So the model is easy right? Well sort of, it will be the object generating the events since the model triggers the changes into the view component to change contents of dialog boxes etc.
So define a VB.NET class that contains an Event
that lookes like a function prototype. And then call it when you want to fire that event. Simple. So …
Public Class Order
Public Event OrderChanged()
Private mItems As List(Of OrderItem) = New List(Of OrderItem)
Public ReadOnly Property Items() As List(Of OrderItem)
Get
Return mItems
End Get
End Property
Public Sub AddItem(ByRef i As Integer)
Dim item As OrderItem = New OrderItem(i)
items.Add(item)
RaiseEvent OrderChanged()
End Sub
End Class
Most of this code is fairly straight-forward. The unusual part was that I defined a variable of type Event
which has a function style definition that I subsequently access through a call to RaiseEvent
. The way this is going to work is that interested parties in this object will declare an instance of my Order object WithEvents
and then also define a public method as Handles [OrderInstace].OrderChanged
. When the model raises the event then all the interested parties will be notified.
Not for the first time, though, VB.NET is holding out on us. It has really defined a multi-cast delegate for OrderChanged()
behind the scenes which is actually a type. If we were to attempt to port this solution to C# then we would actually need to define and reference the delegate ourselves in that way.
The affect of this is to produce something very similar to the Observer pattern. Where the adding of a new delegate instance calls registerObserver()
and the subtracting calls unregisterObserver()
. Calling the delegate instance itself will force the analgous notify()
method, and this is exactly what RaiseEvent
will do.
For the remainder of the model we just need some data-holding for the order item. So lets just add that for fun, no need to discuss.
Public Class OrderItem
Private mid As Integer
Public Sub New(ByVal id As Integer)
mid = id
End Sub
Public Overrides Function ToString() As String
Return Str(mid)
End Function
End Class
Finally, note that the model also has a contract with the presenter and the presenter is dependent upon it. Therefore the presenter will actually call the AddItem() method as a result of actions made in the UI, more on this shortly. Note that the model should not be dependent upon any other component.
Presenter
The presenter is relatively straight-forward. It is the place where this part of the application lives and is responsible for constructing/managing the view and the model. With the removal of the controller (from traditional MVC) the logic about which components handle which responsibilities is blurred slightly. Since our MVP definition could lead us to put simple controller functionality directly into the view (since the view handles the windowing event queue messages). However, we will try and keep this example pure and have the presenter handle all the model prodding and therefore have the view forward calls on to the presenter when something in the model needs to change. Clearly in a simple example like this the benefit is not clear however larger, more complicated components would benefit from this approach.
Public Class OrderPresenter
Private mOrderModel As Order
Private mOrderView As OrderView
Public Sub New(ByRef view As OrderView)
mOrderModel = New Order
mOrderView = view
mOrderView.Init(mOrderModel, Me)
End Sub
Public Sub AddItem(ByVal partId As Integer)
mOrderModel.AddItem(partId)
End Sub
End Class
So exactly what did buy us? There’s two answers to this. Firstly by ‘bunching’ our model calls together in a presenter we’ve allowed ourselves to provide the same functionality from anywhere within our application, so we can hook our presenter/model calls up to a button, or a menu with just a couple of extra code lines.
Even better though, is that we can remove this presenter and replace it with a different one if it offers the same contract. To facillitate this we should probably try and place the view/presenter contract behind an interface to make this distinction clear. I haven’t done this here but it would be a simple case of defining an interface that the presenter implements that handles AddItem(ByVal as Integer)
. A place that this might be useful is, perhaps, when developing presenters that exhibit additional behaviour to the default implementation. For instance when an item is added to an order the ‘auditing’ presenter could additionally write details of the order change to a database or file. Clearly any number of presenters can exist.
View
The view therefore is a subscriber to the Model and is the place where user gestures come in to be forwarded to the Presenter. In this example it is the view for two model objects: Order and OrderItem.
Public Class OrderView
Private mOrderPresenter As OrderPresenter
Private WithEvents mOrderModel As Order
Private last As Integer = 0
Public Sub Init(ByRef model As Order, ByRef presenter As OrderPresenter)
mOrderPresenter = presenter
mOrderModel = model
End Sub
Private Sub OrderChanged() Handles mOrderModel.OrderChanged
ListBox1.Items.Clear()
Dim i As OrderItem
For Each i In mOrderModel.Items()
ListBox1.Items.Add(String.Format("Part id# {0}", i))
Next
End Sub
Public Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
mOrderPresenter.AddItem(last)
last += 1
End Sub
Private Sub OrderView_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
mOrderPresenter = New OrderPresenter(Me)
End Sub
End Class
Our view, of course, is a simple VB form that has a list box in it that lists the items and a button to add an item. Thus:
You will see that the view handles mOrderModel.OrderChanged() as well as events coming from user gestures. The single user gesture that we handle simply forwards a call on to the Presenter to add an item. Note that this is probably overly simplistic. If this were a more concrete example the view might take user input from a combo box, or some other source, and then actually pass model objects onto the presenter.
Now our implementation contains one additional wrinkle and that is that our definition of MVP states that it’s the presenter that handles the construction of the application model and view. For simplicity’s sake here I’ve actually made the view the application entry point. From there it constructs the presenter and effectively “passes” control over in the expected way. To circumvent this we would need to step outside of VB.NET’s application framework which provides useful functionality so is not done here.
Scaling it up
This idea although complete was only presented on a very small scale. If you were to try and develop an entire application this way things are slightly different. The truth of the matter is that MVP is probably only applicable to largeish projects and attempting to use it on very small applications is probably a waste of time.
So, scaling up means two different things with respect to MVP. The model is a little bit amorphous in as much as it is whatever size and shape it is because that’s the way it needs to be to represent the relationships that exist. The view and presenter though are somewhat different. The view/presenter pair will begin by forming groups of controls and ultimately lead to group other view/presenter pairs. So that the contained view/presenters will receive incoming messages from container views. In this way a form of separation can exist. It makes sense then that the contained components know nothing about their surroundings. Otherwise they can not easily be contained in other components and actually become glued to a specific container.
3 replies on “MVP (a.k.a MVC) in VB.NET”
This link of the solution is broken, pleased could you correct it?! i reallly want to see it? or could you sent it to my mail?
Best Regards
Sorry, about that. The link is fixed now.
Hi,
thanks, but I think you shouldn’t access direchtly form view to model
For Each i In mOrderModel.Items()
ListBox1.Items.Add(String.Format(“Part id# {0}”, i))
Next
this action should be via presentation module. I think You need a method in OrderPresenter.vb that brings the “items” from the “Model” and sends it to the “View”.
Thanks