This posting shows how to abstract the user interface in a WPF MVVM application. We will see how the IUserInterface interface helps us reducing the coupeling between the ViewModel classes and UserControls / Windows.
Note: This posting is part of a series. See MVVM-Library for other parts and download.
It has been some time ago since I published the last part “Commanding”. I hope you didn’t loose the interest in my blog meanwhile😉
Today, we will concentrate on another, very important aspect: Letting the ViewModels open new Views (in fact we are only preparing for that step).
As the ViewModels should not know about their concrete views, it would be very bad to write code like that in a ViewModel class:
Window window = new Window(); window.DataContext = new FooViewModel(); window.Child = new FooView(); window.Show();
It really hurts that this code brings a very strong relationship between ViewModel and a concrete control. Almost all advantages we reached by using MVVM would be destroyed by those lines of code. We couldn’t test our ViewModels anymore and using them with different views would be impossible now.
What would be great to have
Wouldn’t it be great to only deal with ViewModels?
Then we only had to create the ViewModel and “let it be shown”.
Abstracting the User Interface
What we need is to abstract the real user interface with its Controls and Windows.
You may agree that the user interface performs two central tasks: Showing “elements” (such as Windows, Dialogs, TrayIcons, …) and hiding them. In addition, “elements” might have parents. One more aspect is the notification: You may want to know when an “element” is shown, when it is closed and maybe even hinder closing one of them.
These thoughts resulted in the following two interfaces:
While IShowable represents an “element” on the user interface, IUserInterface abstracts the user interface itself.
IUserInterface takes the jobs, we defined to be characteristic for an user interface: showing elements, closing elements, abstracting the parent-child relations and notification about these actions.
It only deals with IShowables. The default implementation uses IShowablePresenters it gets from a PresenterFactory in order to display the IShowables.
Do not get irritated because of the event names starting with “Weak”. This only means that the subscriber has to keep a reference to registered handlers. The reason behind that is to allow Garbage Collection of subscribers.
You may think now: “Well, then every UserControl I create has to implement IShowable now.”
That’s wrong. There is a difference between a control and an IShowable: An IShowable is more like a container; you could say a “Window” is an IShowable. It may be confusing now and it is hard to explain what an IShowable really is. You could also say: An independent part of the UI that is OS specific. As said, this includes Windows (as they appear as one unit and all applications have “Windows” as something in common). I hope that will be clearer after you have read this article (and the following one).
The class diagram points out another important characteristic of an IShowable: There is no direct connection to ViewModels. It may exist one, but there doesn’t have to. I will explain the relation between ViewModels and IShowables further in the following post.
An IShowable is always associated with one IUserInterface which displays it. Never, an IShowable will be able to display itself. Optionally, it may have a parent.
All the Show methods expect an IUserInterface as parameter. They can not be called if the IShowable is shown by another IUserInterface. In the default implementation, they are implemented as simple calls to IUserInterface.Show with this as a parameter. However, they set the UserInterface property to the passed IUserInterface. Therefore, you won’t be able to call IUserInterface.Show directly.
Showing / Closing a Showable
- The user creates a Showable and calls its Show method passing an IUserInterface as parameter.
- The Showable sets the passed IUserInterface as its UserInterface Property.
- It calls IUserInterface.Show passing itself as parameter
- The UserInterface lets the PresenterFactory create an IShowablePresenter for the Showable (Note: SetShowable and SetParent are no real methods calls – they are value settings in the constructor)
- UserInterface raises the Shown event
- Showable actualizes its IsShown and Parent properties and raises the Shown event (not included in the diagram)
- The ShowablePresenter gets shown.
It might sound strange that Shown gets fired before the ShowablePresenter is requested to show the Showable. Otherwise, when using ShowDialog, those events would be raised too late.
- The user calls Showable.Close
- The Showable calls UserInterface.Close passing itself as parameter
- The closing event is fired. Subscribers are now able to cancel the closing process
- The closed event is fired
- The IShowablePresenter is requested to close and gets disposed
We successfully abstracted the user interface of an application. Now we need to establish a connection to the ViewModels.
So the next part will be about the ViewModels again and the question how they can (or should) use IShowables and the IUserInterface. I will introduce a Showable subclass that is able to display a special kind of ViewModels and one that represents Messages.
I did not update the MVVM-Library yet, as there is still missing a part to fully understand IUserInterface and use it practically. Without this part, there can not be an example, either.