Click here to Skip to main content
15,867,994 members
Articles / Desktop Programming / WPF

How to make WPF behave as if MVVM is supported out of the box

Rate me:
Please Sign up or sign in to vote.
4.85/5 (25 votes)
2 May 2012CPOL6 min read 49.1K   31   17
Switching the code behind to play as ViewModel to get easier development workflow

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: 

XML
<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: 

C#
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:

C#
namespace ViewModelAsCodeBehindTrick.Views
{
    /// <summary>
    /// Interaction logic for FirstView.xaml
    /// </summary>
    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:

  1. Change the name of the class from FirstView to FirstViewModel.
  2. Change the namespace to ViewModels.
  3. Remove the constructor that calls InitializeComponent (this method obviously has no place in the ViewModel).
  4. Implement INotifyPropetyChanged, so that our ViewModel will be prepared for DataBinding later on.

Eventually, our file would look like this:

C#
namespace ViewModelAsCodeBehindTrick.ViewsModels
{
    /// <summary>
    /// Interaction logic for FirstView.xaml
    /// </summary>
    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".

C#
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:

XML
<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:

image

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:

XML
<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…

image

And this is despite the fact that in design time, it *does* work:

image

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:

C#
public class ViewBase : UserControl
{
}

Instead of having the View inherit directly from UserControl, it will now inherit from ViewBase:

XML
<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…

image

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!

C#
this.GetType().GetMethod("InitializeComponent").Invoke(this, null);

If we run the application now we'll see that it's working! 

image

Only if we return to Visual Studio we'll see that in now works in runtime, but... not in design time.

 image

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:

C#
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:

  1. Our ViewModel now resides under the View in Solution Explorer. Very helpful IMO.
  2. No more redundant CodeBehind!
  3. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Sela
Israel Israel
Elad Katz is Senior Architect at Sela. He specializes in WPF, HTML5 and ASP.NET, with more than 10 years of experience in the industry in various .NET technologies.

Comments and Discussions

 
QuestionCode behind is not necessarily a bad thing Pin
Anthony Steer19-Apr-18 23:54
Anthony Steer19-Apr-18 23:54 
QuestionSeems a lot of effort Pin
Sacha Barber18-Nov-12 22:48
Sacha Barber18-Nov-12 22:48 
AnswerRe: Seems a lot of effort Pin
Elad R. Katz4-Mar-14 10:52
Elad R. Katz4-Mar-14 10:52 
GeneralMy vote of 4 Pin
Iftikhar Akram9-Nov-12 3:22
Iftikhar Akram9-Nov-12 3:22 
GeneralRe: My vote of 4 Pin
Elad R. Katz4-Mar-14 10:55
Elad R. Katz4-Mar-14 10:55 
GeneralNice! Pin
Omar Gameel Salem28-Jul-12 1:19
professionalOmar Gameel Salem28-Jul-12 1:19 
GeneralRe: Nice! Pin
Elad R. Katz28-Jul-12 5:05
Elad R. Katz28-Jul-12 5:05 
Questioninteresting Pin
BillW339-May-12 7:42
professionalBillW339-May-12 7:42 
AnswerRe: interesting Pin
Elad R. Katz9-May-12 12:21
Elad R. Katz9-May-12 12:21 
GeneralMy vote of 4 Pin
andrew45827-May-12 8:29
andrew45827-May-12 8:29 
GeneralObviously. It is not supported out of the box... Pin
Elad R. Katz9-May-12 12:20
Elad R. Katz9-May-12 12:20 
QuestionMy vote of 4 Pin
Ankush Bansal4-May-12 2:51
Ankush Bansal4-May-12 2:51 
AnswerRe: My vote of 4 Pin
Elad R. Katz4-May-12 4:53
Elad R. Katz4-May-12 4:53 
GeneralRe: My vote of 4 Pin
Ankush Bansal4-May-12 21:09
Ankush Bansal4-May-12 21:09 
GeneralRe: My vote of 4 Pin
Elad R. Katz9-May-12 8:13
Elad R. Katz9-May-12 8:13 
Question*Cough ahem* Pin
Pete O'Hanlon2-May-12 5:24
subeditorPete O'Hanlon2-May-12 5:24 
AnswerRe: *Cough ahem* Pin
Elad R. Katz2-May-12 10:09
Elad R. Katz2-May-12 10:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.