hen building complex desktop applications, different individuals or groups of developers often need to collaborate. On some projects, you can separate the layers of the system and assign different individuals to develop each layer. For example, programmer A builds the user interface, programmer B and C build the business logic layer, and programmer D is responsible for the database. In the real world however, work is more often split along functional lines where a single developer is responsible for the user interface, usiness logic, and database layer of a specific application module. Under these conditions, different programmers cooperate to develop components in each layer concurrently. Their components are then integrated during the build process.
This cooperative approach is well suited to the business logic layer. The COM and .NET framework both allow independently developed component to be easily integrated and to function seamlessly together as an application. The same is not true of elements in the User Interface and Data layers. While one can develop using component technology and techniques in both these layers, there are no pre-existing frameworks that allow the independently developed pieces to be easily integrated into a seamless application. This article will explore a solution to this problem in the User Interface layer.
When the application development tasks are cut along functional lines, developers are independently building dialogs for the same application. The problem lies in the integration of these screens and dialogs to produce a final consistent application. In this article I will demonstrate a basic user interface framework that uses the Model-View-Controller (MVC) pattern to leverage Visual Studio.NET’s capabilities.
The Model-View-Controller Pattern
The Model-View-Controller pattern was created out of a need to cleanly separate the display logic, business logic, and data in an application and to properly coordinate their interactions. Take for example a financial application where the same information can be displayed in two separate documents, one as numeric values and one as a chart. If the data changes, both documents must be automatically updated to reflect the changes.
The MVC pattern has three classes:
The Model represents the underlying data for the application. It can be a series of business objects, or even the database itself.
The View represents the user interface element. It can be a form, a control, or a web page.
The Controller coordinates interaction between the View and the Model. It can validate user-entered data and raise exceptions where necessary. It passes data changes to the Model.
The Model, the View, and the Controller interact with each other using direct method calls and by raising events. The sequence generally follows the following sequence
- The user inputs data into a View
- The View notifies the Controller of a change by using the HandleEvent function
- The Controller modifies the Model directly using its properties
- The Model sends out events to all its observers to inform them of the change
Implementing the MVC pattern implies also implementing the Observer pattern to allow the objects to sending and receive events. The Observer pattern has two classes:
- A Subject?the source of events.
- an Observer?which registers with a subject and is notified of changes through events
![]() | |
Figure 1: The Model-View-Controller implies the implementation of the Observer pattern to coordinate events between classes. |
There are multiple ways to implement the MVC pattern. Patterns are, by their nature, abstract and open to interpretation. This implementation behaves as follows:
- The Model sends events to the View informing it of new or modified data
- The View communicates directly with the Controller to inform it of changes made in the user interface
- The Controller modifies the Model directly to make changes to the data.
Implementing the MVC Pattern
The sample application illustrates a work situation where multiple programmers each independently develop their own pieces of the user interface. Those separate pieces are then integrated together at run time into a common visual framework. You can think of each piece as an add-in or snap-in. Each one adds custom functionality to an otherwise empty user interface shell. Consider a standard form. If two different programmers attempt to add functionality to the same form at the same time, you typically end up with either two versions of the form, which you would later need to merge into one coherent whole, or worse, one programmer inadvertently overwrites the other’s work. However, if each programmer works separately on a discrete piece of the form?for example a panel, the menu, a toolbar, or a tab control, and those pieces function independently, you can easily integrate them in code, and they will work together as planned.
To make this collaboration work, you need to introduce another pattern into the mix: the Mediator pattern. A Mediator coordinates the interaction between other classes. The sample code uses Mediators to initialize one or more Views. The Mediator thus becomes the entry point for each programmer’s add-ins. The Mediator’s job is to coordinate interaction between the user interface framework and the View(s) that it controls. For example, the Mediator would add items to the main menu, and react to their click events by displaying the appropriate View.
The sample code (see the download link in the left column of this article) has three projects:
- The MVCLibrary project contains the abstract classes for the framework
- The MVCMediators project is an implementation of the abstract classes. These represent a sample add-in to the framework.
- The MVCSample project contains an empty user interface framework for the application and includes the application’s primary window (in this case an MDI container form), which is responsible for loading the various MVCMediator implementations.
To get started, look at the code in MVCLibrary project, which contains the abstract classes (see Table 1) that programmers use to create their implementations.
Table 1 shows the classes in the MVCLibrary project, their relationship to a pattern, and a brief description of each.
Pattern | Class | Description |
Observer Pattern | clsSubject | Base class for classes who will be sending out events |
IObserver | Interface implemented by classes who need to register for events from other classes | |
clsEvent | Generic event class. It can be used as is, or specialized for special needs. | |
MVC Pattern | clsModel | Base class that your Model must inherit from. |
frmView | Base class for implementing a new View. | |
clsController | Base class for implementing new Controllers. | |
Mediator | clsMediator | Base class for creating new mediators. |
Synchronizing Changes
When a user clicks one of the menu items, the framework fires the viewCustomerList_Click event-handler, which displays a Customer List View.
objView = New frmCustomerListView() objView.Initialize(Me.Model) If Not Me.Container Is Nothing Then objMDIPArent = Me.Container objView.MdiParent = objMDIPArent End If objView.Display()
If the user changes any data in the screen, the Mediator relays the changed data to the Controller to be validated and processed. To see how this works, change the name of one of the customers by editing the label in the List View. When you press Enter or click away from the label, the AfterLabelEdit event goes off.
Dim objListItem As System.Windows.Forms.ListViewItem Dim objEvent As MVCLibrary.clsEvent objListItem = lvwCustomers.Items(e.Item) objEvent = New MVCLibrary.clsEvent() With objEvent .Name = "CustomerNameChange" .Value = e.Label .ValueEx = objListItem.Tag End With Try mobjController.HandleEvent(objEvent) Catch objError As System.Exception MsgBox(objError.Message) e.CancelEdit = True End Try
The AfterLabelEdit event creates a new clsEvent instance and populates it with information about the changed data. Then the View notifies its Controller of the change by calling the HandleEvent method. The notification call occurs within a Try…Catch block because the Controller may return an error if the data is not valid. For example, the business rules might state that the customer name cannot be blank. The Controller’s HandleEvent method validates the Event’s value, catches the error condition, and raises an error.
Public Overrides Sub UpdateData(ByVal NewEvent As MVCLibrary.clsEvent) Dim objCusotmers As clsCustomers Dim objCustomer As clsCustomer Select Case NewEvent.Name Case "CustomerNameChange" If Len(NewEvent.Value) < 1 Then Err.Raise("50000", "clsCustomerListController", _ "Name cannot be blank") End If objCusotmers = Me.Model objCustomer = objCustomers.Item (NewEvent.ValueEx) objCustomer.Name = NewEvent.Value End Select End Sub
If the data is valid, the Controller makes changes to the Model by retrieving the appropriate Customer instance from the collection and changing its Name property.
When this change occurs, it must be propagated to other Views that might be relying on the data. Therefore, in clsCustomer, the Name property's Set code notifies any observers that a change has occurred by creating a new instance of clsEvent, setting its properties, and passing the changes along using the parent's (clsCustomers) Notify method. The Notify method is a member of the base class clsSubject. It cycles through the list of attached observers and sends the event on to them.
Public Property Name() As String Get Name = mstrName End Get Set(ByVal Value As String) Dim objEvent As MVCLibrary.clsEvent mstrName = Value objEvent = New MVCLibrary.clsEvent() With objEvent .Name = "CustomerDataChanged" .Value = Me End With mobjParent.Notify(objEvent) End Set End Property
Views all register with the Model when they are initialized. That means that any other currently displayed View will receive events and be able to adjust its information immediately. There's no need for the user or the application to refresh the screen to see the new data, everything happens in real-time.
To see this in action, open up two or three instances of the Customer List form, change the name of a customer on one of the lists and press enter. You'll see the name updated immediately in all Views.
The sample project also has a New Customer View you can experiment with. To activate this view, click File, then New, then Customer. The New Customer View displays differently than the Customer List View; it's a modal form, not an MDI child, but it behaves the same way. When you click OK to create a new customer, its Controller validates the entered data. If the data is valid, the Controller makes modifications to the Model, which in turn notifies all attached observers about the new customer. Try it out and see what happens.
I hope the sample helps you solve the problem of coordinating multiple programmers working simultaneously on a single UI. Although the sample project code is not robust enough to include directly in a real project, you should be able to use it as a starting point. There are several things you can do to improve the sample project, such as:
- Load the list of Mediators from an XML file, and create them using dynamic binding.
- Replace the even names with enumeration values.
- Add a Windows menu that lists all open windows, and lets you tile and cascade them.
- Give windows the ability to trap standard menu and toolbar events (such as Save from the main menu)