Introduction
Few aspects of MVVM hit the spot with me and I tried to see what it will take to build the simplest MVVM. I started with the very basic concept of creating, binding, and few features as I needed them. The following code is simple, extensible, and provides an easier way to follow some MVVM concepts. My approach is that The MVVM design pattern is a set of recommendations not a set of rules…
Scaffolding
This project provides the frame and building blocks for the MVVM pattern, and few other basic tools. Most of the Kung-Fu takes place in the “CreateAndInject” file which contains a class with the same name. This class exposes one static
method named “CreateAndBindViewAndViewModel
” which instantiates the objects, binds them and finally injects them where I want (details in the next 3 sections).
The Scaffolding
project also supports reading configuration data, it includes the basic Interfaces few events, delegate and support data classes, and initial commanding (primitive and will get extended on the next installment).
I’ll start with the View-ViewModel (V-VM) definition which is contained in “ModelViewRelation
” class (in a file of same name). Here I specify the names of the 2 types (using string
) and a display name that may appear if needed. For good measure, I also allow this definition to include an object (will come in handy when I try to inject data into a V-VM).
The creation of the VM and the V is done by reflection and for that I need (beside the type names) the assembly in which the V and VM types are located and the namespaces for the types. The “ModelViewRelation
” contains default values for those string
s; these values can also be loaded programmatically at run time, or configured in the ‘ServiceReferences.ClientConfig’ file:
<configuration />
<appsettings />
<add key="AssemblyVersion" value="1.0.0.0" //>
<add key="AssemblyName" value="HomeGrownMVVM" //>
<add key="ViewModelPath" value="HomeGrownMVVM.ViewModel" //>
<add key="ViewPath" value="HomeGrownMVVM.View" //>
<add key="ModelSuffix" value="Model" //>
<add key="QueryRandomizer" value="3" //>
</appsettings />
Using this class is simple:
var mvr = new ModelViewRelation("Classic CD's", "ClassicView");
In the above line, I implicitly say that the VM type is “ClassicViewModel
”. If I want to bind the “ClassicView
” to the “RockViewModel
”, I have to that:
var mvr = new ModelViewRelation("Classic CD's", "ClassicView"", "RockViewModel");
View and ViewModel Creating
As mentioned, the heavy lifting is done by a static
method name “CreateAndBindViewAndViewModel
” which provides the following MVVM (and so MVVM) activities:
- Create the View and the ViewModel – by
CreateViewModelAndView()
- Bind (and wire up) the View to the ViewModel – by
WireAndBind()
- Inject the View into its host – by delegate:
injectView
This method calls in sequence 3 private
methods - each covers one of the above steps. The static
method “CreateAndBindViewAndViewModel
” within the “CreateAndInject
” class takes 4 parameters and returns true
if all has succeeded.
public static bool CreateAndBindViewAndViewModel(
ModelViewRelation mvRelation, InjectViewDelegate injectView, UIElement host= null, IBaseViewModel substitute = null) {
UIElement view;
object viewModel = CreateViewModelAndView(mvRelation, substitute, out view);
WireAndBind(mvRelation, view, viewModel);
if (view == null)
{
MessageBox.Show("Fail to launch " + mvRelation.ToString());
return false;
}
return injectView(mvRelation, view, host) != null; }
No magic here…
Moving on - instantiate the VM and V
private static object CreateViewModelAndView( ModelViewRelation mvRelation, IBaseViewModel substitute, out UIElement view) {
view = null;
Assembly assembly = null;
object viewModel = null;
if (mvRelation.IsValid == true)
{ var secureName = string.Format(
"{0}, Version={1}, Culture=neutral, PublicKeyToken=null",
mvRelation.AssemblyName,
mvRelation.AssemblyVersion);
try
{ assembly = Assembly.Load(secureName);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if (assembly != null)
{ viewModel = mvRelation.ViewModelName.EndsWith(".this") ?
substitute : CreateTheObject(assembly, mvRelation.ViewModelName); if (viewModel != null)
{ view = CreateTheObject(assembly, mvRelation.ViewName) as UIElement;
}
}
}
return viewModel;
}
Again, no miracles here, but few points worth mentioning:
If this file “CreateAndInject
” is included within the same project of the Views and ViewModel’s, a simpler call to Assembly.GetExecutingAssembly()
can be used.
If the call to this class is only done from the same project of the Views and ViewModels, a simpler call to Assembly.GetCallingAssembly ()
can be used, but unlike C++, C# doesn’t have inline
keyword; therefore you must incorporate the code of this method into the main public
method.
The code I wrote is more general-purpose with the caveat that when the assembly that contains the V and VM types changes its name or version – update to the configuration file is needed. If other changes are expected (Culture, etc.) - move the rest of the ‘full name’ parameters to the configuration file.
A built-in allowance for ‘recycling’ a VM allows rewiring a new View to an existing ViewModel (see example in the “InformingView_NotifyEvent
” method in “BaseViewMode
”). Right now, I do not unbind the existing view when I use that facility since I never needed it – but it can be done. Extending the “ModelViewRelation
” class is extremely simple and so should the unbinding be.
The actual instantiation happens in the “CreateTheObject
” method:
private static object CreateTheObject(Assembly assembly, string objectName)
{
object obj = null;
var objectType = assembly.GetType(objectName);
if (objectType != null)
{ obj = Activator.CreateInstance(objectType);
}
return obj;
}
View-ViewModel Binding
This is the second step which is carried out by the “WireAndBind
” method.
private static void WireAndBind(
ModelViewRelation mvRelation, UIElement view, object viewModel) {
if (view != null)
{
if (viewModel is IBaseViewModel)
{
if (mvRelation.Anything != null)
{ (viewModel as IBaseViewModel).ModelData = mvRelation.Anything;
}
if (view is IDirectData)
{ (viewModel as IBaseViewModel).DirectDataToView = view as IDirectData;
}
(viewModel as IBaseViewModel).LoadData();
}
if (view is FrameworkElement)
{ (view as FrameworkElement).DataContext = viewModel;
}
if ((view is IInforming) && (viewModel is IBaseViewModel))
{ (viewModel as IBaseViewModel).Subscribe(view as IInforming);
}
if ((view is ILaunchChild) && (viewModel is IBaseViewModel))
{ (viewModel as IBaseViewModel).Subscribe(view as ILaunchChild);
}
}
}
Still no miracles (maybe one or two MVVM sins though…), this method checks which interfaces were declared and based on that, performs certain actions. By order of appearance:
- Inject data to the VM utilizing the
ModelData
variable which is part of the IBaseViewModel
Interface and lives in the BaseViewModel
class. This action will take place only if the user sets the optional parameter ‘Anything
’ in the ModelViewRelation
(see example in the “InformingView_NotifyEvent
” method in “BaseViewMode
”). It can be handy for writing Generic ViewModel.
- Pass a reference of the View to the ViewModel in its
DirectDataToView
variable (I know, literal MVVM disciples shake their heads). This crime/sin will take place only if the View implements the IDirectData
Interface (disciples: do not declare this interface). When the View does expose such Interface, the ViewModel
can push to the view a List< IModelViewData>
. But more than that if the ViewModel
up-casts the DirectDataToView
and get a hold of the View. There is hardly ever a need to use this interface.
- Call the Load Data method within the
ViewModel
to allow the VM doing any work needed before the View gets a ‘glimpse’ of the data which will get bound as the next step. LoadData
is part of the IBaseViewModel
Interface and the virtual method lives in the BaseViewModel
and obviously can be overridden by any ViewModel
. The constructor of BaseViewModel
calls its own Initialize
to carry out any activities that need to take place before the LoadData
can be called (e.g. prepare the WCF proxy).
- Bind the View to the
ViewModel
, provided that the View is a FrameworkElement
.
- Register the
ViewModel
to receive custom events of type NotifyEvent
. This subscription will happen only if the View implements the IInforming
Interface. This is not an MVVM standard, nevertheless it allows me to quickly do some testing, overcome the lack of commands, and as a rule don’t implement the interface if you don’t want this channel of communication opened.
- Register the
ViewModel
to receive custom events of type LaunchChildEvent
. This subscription will happen only if the View implements the ILaunchChild
Interface. Same comments as before…
‘Simple’ View Injecting
This part is a little trickier only because I do not know where the new view is to be injected and how to do it, so here I lean heavily on the caller and keep my life simple: The caller must supply me with an injection delegate and I will call it. The injection delegate’s signature takes 3 parameters and returns one:
public delegate UIElement InjectViewDelegate( ModelViewRelation mvRelation, UIElement boundView, UIElement host);
To clarify the injector delegate, it is best to look at an example. For that, I need to spend few words on the demo.
TabControl
with its TabItem
s is a very nice way to quickly switch between different files (Visual Studio) or switch between different views. The demo launches views into a TabControl
while each time it creates a new TabItem
to host the View
.
For the injector example, I will look at the view that is created by the side menu. Below is the Welcome page of the demo.
When clicking on any link in the side menu, a view will be injected into a new tab page which is added into a tab-control as shown in the next picture:
It is not hard to guess that the click on the side menu sends a populated “ModelViewRelation
” class into the “CreateAndInject
” method. Here is the code snippet for that:
private void MenuItemSelected(object obj)
{
var mvRelation = obj as ModelViewRelation;
if (mvRelation != null)
{
CreateAndInject.CreateAndBindViewAndViewModel
(mvRelation, InjectView, null);
}
}
As can be seen, I receive here the populated mvRelation
, I pass in the Inj
ectView
method as a delegate, and I leave the host as a null
(mainly since the host doesn’t exist yet). The mvRelation
gets populated when I construct the Side Menu. This “CreateAndInject
” method will instantiate the MV and the V, bind them and call the injector which is coming up soon.
The injector usually is simpler than this one – this injector is a little longer because it does some more than just injecting a view. Much simpler examples will follow later. Here goes:
private TabItem InjectView(ModelViewRelation mvRelation, UIElement view, UIElement host)
{
var tabPage = CreateTabPage(mvRelation.DisplayName, view);
if (tabPage != null)
{ TabPagesCollection.Add(tabPage);
TopTabPage = tabPage; }
UpdateViwingArea(); return tabPage;
}
private TabItem CreateTabPage(string header, UIElement view)
{
if (view == null)
{
return null;
}
var hdr = new TabHeaderControl(header);
hdr.CloseMeEvent += CloseTabItemEvent; var tab = new TabItem {
Content = view, Header = hdr, HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Stretch
};
hdr.Tag = tab; return tab;
}
The injector starts with calling “CreateTabPage
” to create a new TabItem
. The code in “CreateTabPage
” creates a user control (TabHeaderControl
) to provide means to close the TabItem
. Then it goes to create the TabItem
itself and inject the view – as expected - into the item’s content. Next it places the header control into the header. Lastly, it stores a reference to the new tab in the header control so it knows which one to close.
The injector adds the TabItem
that was created to the TabPagesCollection
. This collection is bound to the ItemSource
of the TabControl
; hence a new TabItem
is added to the TabControl
. This TabItem
includes the injected View
. Next the injector sets TopTabPage
to the new TabItem
, the TopTabPage
is bound to the SelectedItem
of the TabControl
therefore the TabItem
I just added will be on top of all the other. Lastly it updates the visibility of TabControl
based on the number of items it has (0 = collapsed otherwise visible).
View into View Injecting
This demo pretends to be a tool to query a database. So I have created a V-VM that collects, manipulates, and displays the Classic CD’s. The V in this case contains a DataGrid
of all CDs, some status and summary text and the ability to click on any row in the grid to see a details view of the specific CD.
Once the above V-VM was working, I wanted to create a new V-VM to allow the user to perform a query into the database. This new view should include means for the user to specify the query – at least a text box – and a submit button to run the query. Once he runs the query, the results come back and it will be extremely helpful if I can use the same view I build earlier, but I do not want to lose the query portion of the view – I want to inject this first view into my new query view.
It is quite simple. I add a ContenPresenter
to my query V it will be the host of the first V. Here is the XAML for that (in file ClassicQueryView.xaml):
<ContentPresenter Height="Auto"
Margin="7,7,2,2"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Grid.Row="1"
Loaded="ContentPresenter_Loaded"
Tag="[ClassicView]"
Visibility="{Binding ShowQueryResults}"
>
</ContentPresenter>
The “Visibility
” is bound so that it is visible only if a query was submitted.
The Loaded
event will trigger the creation, binding, and injection of the requested View
-ViewModel
. The requested V-VM is specified using the Tag. The home grown format is [View][ViewModel]
or [View]
the last format is used when the current ViewModel
is to be bound to the new View
.
When the Loaded
event is handled, I pick up the info from the Tag
, populate a V-VM Relation class, call the faithful “CreateAndInject
”, and sit back and watch the view being injected.
I’ll start from the end and look at the injector-delegate:
private UIElement InjectTheView(ModelViewRelation mvRelation,
UIElement view,
UIElement host)
{
var presenter = host as ContentPresenter;
if ((presenter != null) && (view != null))
{
presenter.Content = view;
}
return view;
}
It is hard to get simpler code than that. You would expect to find this injector in the ViewModel
of the Query View, but I pushed it back to the BaseViewModel
only because there are quite enough times I want to inject a view into a view and this way if I always use ContentPreseter
I do not have to write any code – just add it to the XAML and handle the event and that is it.
Back to the start – the loaded event was fired; I handle it in the code behind of the Query View (just because I do not have commands yet). Below is the complete code-behind file, include the handler of the Loaded
event (ClassicQueryView.xaml.cs):
using System.Windows;
using System.Windows.Controls;
using VvmScaffolding;
namespace HomeGrownMVVM.View
{
public partial class ClassicQueryView : UserControl, IInforming
{
public ClassicQueryView()
{
InitializeComponent();
}
private void ContentPresenter_Loaded(object sender,
RoutedEventArgs e)
{
if (NotifyEvent != null)
{
NotifyEvent(sender,
new GenericEventArgs(sender));
}
}
public event NotifyEventHandler NotifyEvent;
}
}
The “ClassicQueryView
” class implements the Informing interface hence if it raises the NotifyEvent
and the bound VM will handle it. The event handler takes the sender and places it in a custom GenericEventArg
.
The next step is to look at handling this event, which is done in the ViewModel
; again I push the handler to the BaseViewModel
so I do not have to worry about it anymore. From BaseViewModel
:
protected virtual void InformingView_NotifyEvent(object sender, GenericEventArgs e)
{
var presenter = e.Camel as FrameworkElement;
if (presenter != null)
{ var names = presenter.Tag as string; if (string.IsNullOrEmpty(names) == false)
{ e.Handled = true; var separators = new[] {'[', ']', ' '};
names = names.Trim(separators);
var saNames = names.Split(separators,
StringSplitOptions.RemoveEmptyEntries);
ModelViewRelation mvr = null;
switch (saNames.Length)
{
case 1: mvr = new ModelViewRelation
("Dynamic View", saNames[0], "this");
break;
case 2: mvr = new ModelViewRelation
("Dynamic View", saNames[0], saNames[1]);
break;
}
CreateAndInject.CreateAndBindViewAndViewModel
(mvr, InjectTheView, presenter, this);
}
}
}
Since this handler is in the base class, every class that handles that event overrides this handler. The overriding handler must first call the base handler and continue only if the base hasn’t handled it. After extracting the name/s from the tag, the code populates the ModelViewRelation
. If only one name found it insert the string ‘this
’ as the name of the VM – that will cause the “CreateAndInject
” to skip instantiating a VM. The call to “CreateAndInject
” passes the newly created ModelViewRelation
, an inject-delegate (the one we looked at earlier), the presenter (the sender of the Loaded
event) as the host and itself as a substitute VM (in case substituting is needed).
In this case, the VM for the new V is the existing “ClassicQueryViewModel
”. If I want the new view to be able to launch a child view (see next section), I need to copy the handler for that even from “ClassicViewModel
” into the “ClassicQueryViewModel
”.
Launching Child View
In this demo when I click on a grid row, I want to get a details view of the CD in this row. This details view will be created using a ChildWindow
. The View name is DetailsChildWindowView.xaml and the ViewModel
follows the naming convention DetailsChildWindowViewModel.cs.
This is a case where the VM gets the data injected into it (indirectly) by the launching VM I’ll use the ClassicViewModel
as the launching one. Another difference is that this V-VM is hosted by no other view I was tempted to call it floating view but ChildWindow
may be closer to the truth. The fact that this view has no host is not a problem really, since the Injector will know what to do.
Let me recap: I click on a row, SelectionChanged
event is fired, event is handled by the code behind, launching a V-VM, inject the selected–row-details into the launched V-VM, and show the view.
Here is DataGrid
portion of the XAML of the ClassicView.xaml:
<sdk:DataGrid AutoGenerateColumns="True"
HorizontalAlignment="Left"
Margin="5"
VerticalAlignment="Top"
Grid.Row="1"
ItemsSource="{Binding DataToPresent}"
SelectionChanged="DataGrid_SelectionChanged"
/>
Here is the handler of the SelectionChanged
event in the code behind (ClassicView.xaml.cs):
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if ((NotifyEvent != null) && (e.AddedItems.Count > 0))
{
NotifyEvent(sender, new GenericEventArgs(e.AddedItems[0]));
}
}
Once it has determined there is a selected row, it passes the item (the class PresentingClassicData
, which was bound through the XAML) into the GenericEventArgs
and fires the NotifyEvent
.
The NotifyEvent
is handled by the VM – ClassicViewModel
.
protected override void InformingView_NotifyEvent(object sender, GenericEventArgs e)
{
base.InformingView_NotifyEvent(sender, e);
if (e.Handled == true)
{ return;
}
var r = e.Camel as PresntingClassicData;
if (r != null)
{ var mvvmDef = new ModelViewRelation("Details", "DetailsChildWindowView", "DetailsChildWindowViewModel", r.Details); PopUpView(this, new ModelViewRelationEventArgs(mvvmDef));
}
var dg = sender as DataGrid;
if (dg != null)
{ dg.SelectedItems.Clear();
}
}
PopUpView
is called to launch the view. The PopUpView
is pushed into the BaseViewModel
so every VM can use it and its injector:
public void PopUpView(object sender, ModelViewRelationEventArgs e)
{
CreateAndInject.CreateAndBindViewAndViewModel(e.Holder, ShowDetails);
}
public ChildWindow ShowDetails(ModelViewRelation mvRelation,
UIElement view, UIElement host)
{
var cw = view as ChildWindow;
if (cw != null)
{
cw.Show();
}
return cw;
}
The PopUpView
calls CreateAndBindViewAndViewModel
. It passes to it an injector delegate – ShowDetails
. The injector simply receives the view and makes sure it is ChildWindow
class. Once it is a ChildWindow
, the injector calls Show
on it.
Obtaining Configuration Data
The “ConfigurationManager.cs” file provides a means to obtain configuration data from ServiceReferences.ClientConfig
. I used configuration data to pass configuration data to the application and also to create the initial Side Menu and its V-VM settings.
The “AppSetting
” contains a static
method “GetMenuItems
” which goes through the configuration file and creates a list of 3 string
s (Display Name, View Name, and ViewModel Name).
static public List<threesringholder> GetMenuItems()
{
var list = new List<threesringholder>();
var settings = new XmlReaderSettings();
settings.XmlResolver = new XmlXapResolver();
var reader = XmlReader.Create(configFile);
reader.MoveToContent();
while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.Name == "MenuItem")
{
string d = reader.GetAttribute("Name");
string v = reader.GetAttribute("View");
string vm = reader.GetAttribute("ViewModel");
var item = new ThreeSringHolder(d, v, vm);
list.Add(item);
}
else
{
if (list.Count > 0)
{
break;
}
}
}
}
return list;
}
Similar code in “GetAppSetting
” retrieves value from a Key-Value entrees and another overriding method allows specifying a default value while retrieving the value.
The Side Menu
The Side Menu is created at the WelcomePage
file. It is a ListBox
(of HyperlinkButton
items) bound to a collection of ModelViewRelation
objects.
private void CreateMenuList()
{
MenuItemsList = new ObservableCollection<modelviewrelation>
{
new ModelViewRelation("Classic CD's", "ClassicView"),
};
var list = AppSetting.GetMenuItems();
foreach (var item in list)
{
if ((string.IsNullOrEmpty(item.Name) == true) &&
(string.IsNullOrEmpty(item.View) == true))
{ MenuItemsList.Add(new ModelViewRelation("", ""));
}
else if (string.IsNullOrEmpty(item.ViewModel) == true)
{
MenuItemsList.Add(new ModelViewRelation(item.Name, item.View));
}
else
{
MenuItemsList.Add(new ModelViewRelation
(item.Name, item.View, item.ViewModel));
}
}
}
The first item is created from values in the code; next it calls “AppSetting.GetMenuItems
” to obtain all the string
s from the configuration files. Lastly it constructs “ModelViewRelation
” from the list and adds all of them to the “MenuItemsList
” collection.
This collection is bound to the ListBox
that creates the Side Menu so that the Display name is shown to the user. Clicking on HyperlinkButton
ends up (utilizing the Silverlight commanding) with a call to “MenuItemSelected
”:
private void MenuItemSelected(object obj)
{
var mvRelation = obj as ModelViewRelation;
if (mvRelation != null)
{
CreateAndInject.CreateAndBindViewAndViewModel
(mvRelation, InjectView, null);
}
}
This method creates and binds the V-VM and injects it into a TabItem
that is added to the TabControl
.
Base ViewModel and its Interface
The base model implements the IBaseViewModel
interface that supports the activities by the binder:
public interface IBaseViewModel : INotifyPropertyChanged
{
object ModelData { get; set; }
IDirectData DirectDataToView { get; set; }
void LoadData();
void Subscribe(IInforming informingView);
void Subscribe(ILaunchChild launchingView);
}
It also implements few common methods and properties such as InfoText
, SubmitCommand
, PopUpView
, and handling the NotifyEvent
for View in View request.
Commands
The CommandBase
is a class that simplifies creating a command handler. It allows me to pass two delegates one for the Can Execute and one for the Execute itself.
Summary
With the small VvmScaffolding
project, it is possible to get some basic View-ViewModel benefits.
It is simple enough for me to modify it and build upon it as the need arises. My next step is to try and get commands to work on other controls and other events besides click. The MVVM design pattern is a set of recommendations, not a set of rules…
History