Introduction
I get to teach MVVM pretty often and almost always somebody asks why WPF does not support it "built in".
If we think about it, the CodeBehind and the ViewModel are basically very much alike. They both contain the logic behind the UI, and their sole difference is that since the ViewModel is an all-together a different class, it helps constitute better separation between the logic (ViewModel) and the UI (View).
When you implement MVVM, Visual Studio does not know of the connection between the View and the ViewModel, and therefor you cannot switch between them easily by pressing F7 like you would normally do to switch between the XAML and the CodeBehind. Additionally, it would be helpful if Visual studio would have the ViewModel file "sit" under the View file in solution explorer, the way it does with CodeBehind.
If we think about it, the actual CodeBehind file we don't even need! I would prefer if it wasn't there to begin with so that nobody in my team would be tempted to use it at all. If there's no CodeBehind file, you cannot write CodeBehind, and that's a good thing.
Transforming the CodeBehind to ViewModel
The following idea I've had for quite a while now, and I think I've come with a nice trick to do exactly that. We'll just transform the CodeBehind file to ViewModel, and be able to hold the stick on both ends!
For example, let's assume I have a View named FirstView. The original XAML is going to look like this:
<UserControl x:Class="ViewModelAsCodeBehindTrick.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
</Grid>
</UserControl>
This is the interesting line:
x:Class="ViewModelAsCodeBehindTrick.Views.FirstView"
It makes XAML's parser create a class named FirstView
that
inherits from UserControl
. This class will be joined to the CodeBehind class through the use of 'partial'.
Switching to the CodeBehind, we'll see the following code, that as said before – we don't really need:
namespace ViewModelAsCodeBehindTrick.Views
{
public partial class FirstView : UserControl
{
public FirstView()
{
InitializeComponent();
}
}
}
What we have here is the second part of the FirstView
class that connects to the first part that was created by XAML. All we need to do is:
- Change the name of the class from FirstView to
FirstViewModel
.
- Change the namespace to ViewModels.
- Remove the constructor that calls
InitializeComponent
(this method obviously has no place in the ViewModel).
- Implement
INotifyPropetyChanged
, so that our ViewModel will be prepared for DataBinding later on.
Eventually, our file would look like this:
namespace ViewModelAsCodeBehindTrick.ViewsModels
{
public class FirstViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
}
}
For the sake of demonstration, we'll add a property to our ViewModel so we'll be able to bind to it from the View later on, so that we could see if everything is working
correctly. The following property is just a textual property, with its value being "Hello MVVM".
public class FirstViewModel : INotifyPropertyChanged
{
private string someText;
public string SomeText
{
get { return someText; }
set
{
someText = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("SomeText"));
}
}
public FirstViewModel()
{
SomeText = "Hello MVVM";
}
public event PropertyChangedEventHandler PropertyChanged;
}
All that remains now is to connect the View to the ViewModel. This can be done in all the standard ways (usually I use a ViewModelLocator),
but for this example I used the simplest way possible:
<UserControl ... >
<UserControl.DataContext>
<vm:FirstViewModel />
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding SomeText}" />
</Grid>
</UserControl>
Additionally, I added a TextBlock that is bound to "SomeText" so that we could see if everything is working correctly.
If we now look at the designer preview, we'll see that evidently it does:
Not so fast…
This is where it gets complicated. Without paying too much notice we've created a nasty bug that will be seen only in runtime. If we put our View ("FirstView") on the main window:
<Window x:Class="ViewModelAsCodeBehindTrick.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:v="clr-namespace:ViewModelAsCodeBehindTrick.Views"
Title="MainWindow" Height="350" Width="525">
<Grid>
<v:FirstView x:Name="firstView1" />
</Grid>
</Window>
And run the application, we'll see… nothing. It doesn't work for some reason…
And this is despite the fact that in design time, it *does* work:
So what is it? How come it doesn't work on run time although id *does* work on design time?!
The first instinct of every veteran WPF programmer is to immediately check the Output window to see if there are any Bidning Errors, but that's not going to help here.
There are no binding errors and the Output window is going to be clean. It is an entirely different problem.
(I suggest taking a few minutes to try and figure out yourselves. It took me some time as well... :-)
.
.
.
.
.
.
.
.
.
.
.
Understanding the XAML parsing process
From the XAML file two files are created during compilation:
1. FirstView.g.cs – where the class FirstView sits. This class loads the second file –
2. FirstView.Baml which is our XAML after some sort of compilation (it's actually pre-tokenization – parsing the file in advance so that the loading in runtime would be faster than loading a non-parsed XML file)
The loading and connecting of those two files are being performed in the method InitializeComponent that resides within FirstView.g.cs, only… now that we've got rid of the CodeBehind nobody calls this method. What happens is that nothing loads the BAML file and hence everything stays completely empty. We don't see binding errors because even the Binding is not loaded.
The interesting bit here is that during design time, Visual Studio knows automatically to load and run the BAML file, and this is why in design time is does work.
So all we need to do to fix the problem is to make sure that this method does get called during runtime. But how?
Creating a UserControl that automatically calls InitializeComponent
The solution I found is a nice trick. Instead of having our View implement UserControl, let's make it implement a new class named ViewBase.
Let's create a new class named
ViewBase
that inherits from UserControl
:
public class ViewBase : UserControl
{
}
Instead of having the View inherit directly from UserControl
, it will now inherit from
ViewBase
:
<v:ViewBase x:Class="ViewModelAsCodeBehindTrick.Views.FirstView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:ViewModelAsCodeBehindTrick.ViewsModels"
xmlns:v="clr-namespace:ViewModelAsCodeBehindTrick.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<vm:FirstViewModel />
</UserControl.DataContext>
<Grid>
<TextBlock Text="{Binding SomeText}" />
</Grid>
</v:ViewBase>
(When we change UserControl to v:ViewBase Visual Studio is not going to like it at first and it won't provide intellisense for a few seconds. That's OK.)
All that is left now is to make our newly created class (ViewBase
) call
InitializeComponent
in the constructor.
The problem is that if we try and invoke InitializeComponent
we'll get an error – the method doesn't exist yet. This method is being created in compile time by the XAML Parser…
So we need to call the method in runtime. The method exists at runtime but not at design-time, so we can't call it because it won't compile. Nothing that won't be solved with some Reflection!
this.GetType().GetMethod("InitializeComponent").Invoke(this, null);
If we run the application now we'll see that it's working!
Only if we return to Visual Studio we'll see that in now works in runtime, but... not in design time.
What happens now is that we're trying to call
InitializeComponent
, which works in runtime, but fails in design time. This is going to easily solved – we'll just make sure not to call it in design time:
public class ViewBase : UserControl
{
public ViewBase()
{
if (!DesignerProperties.GetIsInDesignMode(this))
this.GetType().GetMethod("InitializeComponent").Invoke(this, null);
}
}
And now it'll work perfectly for design time AND runtime!
Was it worth the effort?
Bottom line, after writing ViewBase for the first time – it's going to be real easy to use it for the second time. This gives us a few very cool and handy features:
- Our ViewModel now resides under the View in Solution Explorer. Very helpful IMO.
- No more redundant CodeBehind!
- If we're in the XAML file, all we need to do to go to the ViewModel file is just press F7! (Regretfully it doesn't work the other way around, but it's still quite a lot.)
In my opinion it's definitely worth the effort. A huge part of MVVM is aimed toward making the Tools work better, and this article goes in the same direction.
I suggest downloading the code and playing with it. Is it useful to you?
The full code can be downloaded HERE.