Handling UI Events
Another point of departure among these three patterns is how the UI events are handled. For example, consider a button push. In strict MVC, there is no UI event logic in the code behind file for a view; all processing happens in the controller. This means that the view has to expose button press events and any other UI events to the controller for processing. The controller has to attach a handler to each event that it considers significant and process that event accordingly.
In MVP, the code behind for the view preprocesses UI events before passing them on to the controller for processing. If a request is strictly UI-related with no business logic, the view can handle the request and return without involving the presenter.
In MVVM, again this issue is sidestepped due to the capability of XAML-based applications to bind to commands the view model provides.
Let’s look at an example of the difference between MVC and MVP. Assume your application is up and running, displaying a grid of data. The user wants to see the details of one of the rows, so he presses the Show Details button to open a dialog window with more detailed data. In our UI, it is also possible to show details by right-clicking the context menu. Listing 2.4 shows an example of the MVC version.
Listing 2.4. An MVC View Exposing Events to Controller
using
System;using
System.Windows.Forms;namespace
CodeSamples.Ch02_ApplicationArchitecture.Listing04 {public class
SampleForm
:Form
{private
DataGrid
Grid;private
Button
showDetailsBtn;private
Button
maximizeWindowBtn;private
ContextMenu
mnu;public event
EventHandler
ShowDetailsButtonClicked;public event
EventHandler
MaximizeWindowButtonClicked;public event
EventHandler
ContextMenuItemClicked;public
SampleForm() { Grid =new
DataGrid
(); showDetailsBtn =new
Button
(); maximizeWindowBtn =new
Button
(); mnu =new
ContextMenu
(); showDetailsBtn.Click += showDetailsBtn_Click; maximizeWindowBtn.Click += maximizeWindowBtn_Click;var
mnuItem = mnu.MenuItems.Add("Details"
); mnuItem.Click += mnuItem_Click; }void
maximizeWindowBtn_Click(object
sender,EventArgs
e) {if
(MaximizeWindowButtonClicked !=null
) MaximizeWindowButtonClicked(this
, e); }void
mnuItem_Click(object
sender,EventArgs
e) {if
(ContextMenuItemClicked !=null
) ContextMenuItemClicked(this
, e); }void
showDetailsBtn_Click(object
sender,EventArgs
e) {if
(ShowDetailsButtonClicked !=null
) ShowDetailsButtonClicked(this
, e); }public void
Maximize() {//
maximize window
} }public class
SampleFormController
{private
SampleForm
_form;public
SampleFormController() { _form =new
SampleForm
(); _form.ShowDetailsButtonClicked += _form_ShowDetailsButtonClicked; _form.ContextMenuItemClicked += _form_ContextMenuItemClicked; _form.MaximizeWindowButtonClicked += _form_MaximizeWindowButtonClicked; }void
_form_MaximizeWindowButtonClicked(object
sender,EventArgs
e) { _form.Maximize(); }void
_form_ContextMenuItemClicked(object
sender,EventArgs
e) { ShowDetailsForm(); }void
_form_ShowDetailsButtonClicked(object
sender,EventArgs
e) { ShowDetailsForm(); }private void
ShowDetailsForm() { } } }
In MVC, the controller receives the button press event directly and executes the appropriate logic. It also attaches a separate handler to the context menu event. All of these event handlers can call the same method to do the work to actually process the event, but there is still the need to have multiple event handlers. If yet another UI method for viewing details is added (such as a hotkey), the view needs to be updated to expose this additional event handler, and the controller needs to be updated to process the event. Also note that the user request to maximize the window is sent out to the controller, which calls the proper method on the form.
Now consider the equivalent example but in an MVP configuration, as demonstrated in Listing 2.5.
Listing 2.5. An MVP View Exposing Events to Presenter
using
System;using
System.Windows.Forms;namespace
CodeSamples.Ch02_ApplicationArchitecture.Listing05 {public class
SampleForm
:Form
{private
DataGrid
Grid;private
Button
showDetailsBtn;private
Button
maximizeWindowBtn;private
ContextMenu
mnu;public event
EventHandler
ViewDetailsRequested;public event
EventHandler
MaximizeWindowButtonClicked;public
SampleForm() { Grid =new
DataGrid
(); showDetailsBtn =new
Button
(); maximizeWindowBtn =new
Button
(); mnu =new
ContextMenu
(); showDetailsBtn.Click += showDetailsBtn_Click; maximizeWindowBtn.Click += maximizeWindowBtn_Click;var
mnuItem = mnu.MenuItems.Add("Details"
); mnuItem.Click += mnuItem_Click; }void
maximizeWindowBtn_Click(object
sender,EventArgs
e) {//
maximize window
}void
mnuItem_Click(object
sender,EventArgs
e) {if
(ViewDetailsRequested !=null
) ViewDetailsRequested(this
, e); }void
showDetailsBtn_Click(object
sender,EventArgs
e) {if
(ViewDetailsRequested !=null
) ViewDetailsRequested(this
, e); } }public class
SampleFormPresenter
{private
SampleForm
_form;public
SampleFormPresenter() { _form =new
SampleForm
(); _form.ViewDetailsRequested += _form_ShowDetailsButtonClicked; }void
_form_ShowDetailsButtonClicked(object
sender,EventArgs
e) { ShowDetailsForm(); }private void
ShowDetailsForm() { } } }
This MVP example implements an event filtering process in the view to abstract away the various ways for a user to request an action. Instead of the presenter adding event handlers for the button and context menu events, the code behind in the view does that. The view also defines a new event called ViewDetailsRequested. Each of the event handlers in the view subsequently raise the same ViewDetailsRequested event, which the presenter creates a handler for. The view filters the individual UI requests into a general action event for the presenter to handle. With this structure, if we add our hotkey to show details only, the view needs updating to handle the additional functionality. The interface and presenter remain unchanged.
Also note in this implementation that the view itself takes care of the window maximize request. Because no business logic is required to fulfill this request, the view performs the requested action without involving the presenter. If the presenter needs to know of the request for some reason, we can always refactor to send this message out to the presenter.