Preferred method for binding in MVVM, Data Template in Resources file or just DataContext in View itself?

23,068

Solution 1

Its important to realize when working with WPF that there are two layers: the data layer (DataContext) and the UI layer (the XAML).

Bindings are used to pull data from the data layer into the View layer.

When you write

<UserControl.DataContext>
    <local:DataBoundMVVMChartViewModel/>
</UserControl.DataContext>

You are telling WPF that it should create a new instance of DataBoundMVVMChartViewModel and use it for the data layer of that UserControl.

When you write

<Label Content="{Binding HelloString}" />

you are telling the Label to look in its data layer (the DataContext) for a property called "HelloString", and use it for the Content property.

If the property "HelloString" does not exist in the data layer (which it does not unless you set the DataContext like you did with <UserControl.DataContext>), the binding will fail and nothing gets displayed except for a binding error in your output window.

Your DataTemplate from your ResourceDictionary is something different from the DataContext.

In fact, a ResourceDictionary is just what it sounds like - a dictionary of resources that WPF can use in the application when needed. But objects in the Dictionary are not by default part of the application's UI itself. Instead, they need to be referenced in some way to be used.

But back to your DataTemplate

<DataTemplate DataType="{x:Type vm:DataBoundMVVMChartViewModel}">
    <vw:DataBoundMVVMChart/>
</DataTemplate>

WPF uses DataTemplates to know how to draw specific types of objects. In your case, this DataTemplate is telling WPF that anytime it needs to draw an object of type DataBoundMVVMChartViewModel, it should do so by using a DataBoundMVVMChart.

To insert an object into the UI, a Content property is normally used, such as

MyLabel.Content = new DataBoundMVVMChartViewModel();

or

<ContentControl Content="{Binding SomeDataboundChartViewModelProperty}" />

I actually started out learning MVVM with the exact same article you linked in your question, and had a lot of trouble figuring it out as well, which lead me to doing a little blogging about WPF/MVVM aimed specifically for beginners like me :)

If you're interested, I have a few blog articles about WPF/MVVM that may help you understand the technology better.

As for your actual question:

Preferred method for binding in MVVM, Data Template in Resources file or just DataContext in View itself?

I prefer using a DataTemplate in the .Resources somewhere, as then your UI is not specifically tied with one specific DataContext.

This is especially important when creating re-usable controls with MVVM. For example, if you have a CalculatorUserControl, and you assigned it a DataContext in the control itself such as with <UserControl.DataContext>, then you could never use that CalculatorUserControl with any other DataContext other than the one that is created when the control is created.

So typically I set the DataContext for the entire application once at startup, and use DataTemplates to tell WPF how to draw the different ViewModels or Models in my application.

My entire application exists in the data layer, and the XAML is merely a user-friendly interface to interact with the data layer. (If you want to see a code sample, check out my Simple MVVM Example post)

Solution 2

If you want to use implicit DataTemplates you have to use the view-model-first approach, you however add a view to your DockPanel without a view-model as its context. If you simply add the respective view-model (to a ContentControl or ItemsControl), the view will be created automatically from the DataTemplate with the view-model as its DataContext.

e.g.

<ItemsControl Name="ic"/>

ic.Items.Add(new DataBoundMVVMChartViewModel());

I personally prefer this over view-first (e.g. adding a view which then assigns its own DataContext), because you usually want references to the view-models, the view is only secondary and manipulated indirectly by interacting with the view-model.

Share:
23,068
djangojazz
Author by

djangojazz

I love technology and music. I am advanced at TSQL and also like to try to create in C# 3.5 through 4.6.2 when I can. I tend to favor Data Access with Linq, Entity Framework, ADO.NET, and SQL being my strong points. But I also dab in WPF and Prism and sometimes WinForms when forced to. I like all technologies really and wish there was more time in the day to do everything I want to learn but having kids and a full time job slows you down a little bit.

Updated on July 09, 2022

Comments

  • djangojazz
    djangojazz almost 2 years

    This one has got me stumped as I THOUGHT I looked at everything but I must be missing something. I have went off the traditional MVVM pattern from the MSDN magazine:

    http://msdn.microsoft.com/en-us/magazine/dd419663.aspx

    while learning MVVM. However I usually copy most of the code and then replace it as I need to but today I wanted to build something from scratch and saw that there may be more to it than I thought. MVVM appeared to not work with bindings when I used the resource dictionary but does with datacontext directly. This question ultimately wants to find other developers suggested use of binding they find.

    Summary of question is this: Why does the 'DataTemplate' in the Resource Dictionary appear to not work shown below but a direct 'DataContext' method does with a view right away for bindings?

    Is it because I am doing a mixture of code behind with settings views in the code behind. Or potentially because of something else. It appears my property and it's implementation are set correct in the viewmodel if I set the 'DataContext' directly in the View's XAML, but why not in the Resource Dictionary? I thought the advantage of that method was you could set a bunch of relationships all at once. I am curious if there is some other setting need to be done to get it to work. It is curious in the main example of the MVVM method they use Data Templates yet it appears there is more to setting them up than I am doing to get their 'bindings' to work.

    WHAT DID NOT WORK FOR ME:

    I attempted to do some very basic stuff in a main window xaml, leaving some of my code out to make it even simpler:

    Main Window XAML:

    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/charts" x:Class="WPFTesting12_2.MainWindow"
            Title="MainWindow" Height="350" Width="525">
        <Window.Resources>
            <ResourceDictionary Source="Resources.xaml"/>
        </Window.Resources>
        <Grid>
            <DockPanel x:Name="dockpanel">
                <Menu DockPanel.Dock="Top" Height="30">
                    <MenuItem Header="Charting">
                        <MenuItem Header="MVVMDataBound" x:Name="mnuDataBoundSeriesMVVMCharting" Click="mnuDataBoundSeriesMVVMCharting_OnClick"/>
                    </MenuItem>
                </Menu>
                <TextBlock Height="5" Background="Black" DockPanel.Dock="Top" />
                <DockPanel x:Name="dockchildren" DockPanel.Dock="Bottom"/>
            </DockPanel>
        </Grid>
    </Window>
    

    Main Window Code Behind:

    public partial class MainWindow : Window
        {
    
            public MainWindow()
            {
                InitializeComponent();
    
                this.WindowState = WindowState.Maximized;
            }
    
    
            private void mnuDataBoundSeriesMVVMCharting_OnClick(object sender, RoutedEventArgs e)
            {
                View.DataBoundMVVMChart c = new DataBoundMVVMChart();
    
                dockchildren.Children.Clear();
                dockchildren.Children.Add(c);
            }
        }
    }
    

    Resource Dictionary:

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:vw="clr-namespace:WPFTesting12_2.View"
                        xmlns:vm="clr-namespace:WPFTesting12_2.ViewModel"
                        >
      <DataTemplate DataType="{x:Type vm:DataBoundMVVMChartViewModel}">
        <vw:DataBoundMVVMChart/>
     </DataTemplate>
    
    <Style TargetType="MenuItem">
            <Setter Property="Background" Value="Wheat"/>
        </Style>
        <Style TargetType="Menu">
            <Setter Property="Background" Value="Wheat"/>
        </Style>
    
    </ResourceDictionary>
    

    View for DataBoundMVVMChart.xaml:

    <UserControl x:Class="WPFTesting12_2.View.DataBoundMVVMChart"
                 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:local="clr-namespace:WPFTesting12_2.ViewModel"
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300">
        <UserControl.Resources>
            <ResourceDictionary Source="..\Resources.xaml"/>
        </UserControl.Resources>
        <Grid>
            <DockPanel>
                <Menu DockPanel.Dock="Top">
                    <MenuItem Header="TesterContent"/>
                </Menu>
                <Label DockPanel.Dock="Bottom" Width="300" x:Name="label" Height="50" Foreground="Blue" FontSize="24"  Content="{Binding Path=HelloString}"/>
            </DockPanel>
        </Grid>
    </UserControl>
    

    ViewModel to bind to the View above:

    namespace WPFTesting12_2.ViewModel
    {
        class DataBoundMVVMChartViewModel : INotifyPropertyChanged
        {
            private string _HelloString;
    
            public string HelloString
            {
                get { return _HelloString; }
                set 
                {
                    _HelloString = value;
                    RaisePropertyChanged("HelloString");
                }
            }
    
            public DataBoundMVVMChartViewModel()
            {
                HelloString = "Hello there from the ViewModel";
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void RaisePropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

    Okay now the binding will fail in the view but yet the resources of the color will come in. So I was thinking I did something wrong but code behind will get the property right away. So let's move on:

    WHAT DID WORK:

    Magicially if I just add these four lines to the view:

    Add this to the declarations in the 'UserControl' segment at top:

    xmlns:local="clr-namespace:WPFTesting12_2.ViewModel"
    

    Then set a reference to the UserControl's DataContext:

    <UserControl.DataContext>
            <local:DataBoundMVVMChartViewModel/>
        </UserControl.DataContext>
    
  • djangojazz
    djangojazz almost 11 years
    I am confused by your answer as I thought you had to create the views manually still as that is what the data template did was relate an existing view object to an existing viewmodel object. Are you meaning the view should be made of the dock panel and it could have many relations to many viewmodels? You will have to forgive me as I am still trying to learn MVVM on a deeper level.
  • djangojazz
    djangojazz almost 11 years
    I follow what you are writing but you lose me when you state the part about inserting an object into the UI. Are you speaking of ther outer layer at the MainWindow.xaml or the inner at the called userControl? I am just confused as I get in essence what the Data Template is doing but not why it is not working if I am referencing the ResourceDictionary and it picks up my color setttings but not the template part. When I look at the example it appears everything is hooked up right, yet everytime I usually do MVVM I copy that whole example. Thanks for your examples I will take a look at them.
  • H.B.
    H.B. almost 11 years
    DataTemplates are blueprints for what should be created to display a certain data-type. If you add a view-model to some control that supports it (DockPanel only allows controls (thus views) to be added, not data) said blueprint will be executed and a view for that view-model is created. If you use DataTemplates you should not create views yourself, that is the whole point of using them. In terms of relationships it's usually 1 view-model to 1 view, what i said has nothing to do with many-to-many relationships.
  • Rachel
    Rachel almost 11 years
    @djangojazz WPF allows you to insert any object into the UI tree. By default WPF draws all items it doesn't know how to draw with a TextBlock showing the .ToString() of the object, however DataTemplates can be used to tell WPF how to render the object. As an example, change your menu click command to dockchildren.Children.Add(new DataBoundMVVMChartViewModel());. You would normally see something like "WPFTesting12_2.ViewModel.DataBoundMVVMChartViewModel" in the UI, however because of your DataTemplate in <Window.Resources>, it will get drawn using a DataBoundMVVMChart instead.
  • djangojazz
    djangojazz almost 11 years
    Okay I guess then my only frame of reference is the MSDN example then and in that example they do create the views. So is my template not working due to the nature of data not being allowed in a dock control? If I changed it to be the more traditional tab control in the MSDN example it would still need a portion of the viewmodel to add things like: ic.dockpanel.Add(new DataBoundMVVMChartViewModel));? I am basically trying to wonder if the layer of control is always at the top viewmodel and you can't just start a control with access to a viewmodel with data templates in the middle of nowhere
  • Rachel
    Rachel almost 11 years
    @djangojazz (Oops, I forgot DockPanels only allow UI objects to be added to them, so you'd need to use a control that allows any object to be added for the example in my above comment to work, such as a <ContentControl> or a <Label>: MyContentControl.Content = new DataBoundMVVMChartViewModel();)
  • Rachel
    Rachel almost 11 years
    @djangojazz The MSDN example creates a TabControl with the ItemsSource property bound to a collection of Workspace objects. What that does behind the scenes is create a TabItem for each object in the collection, and inserts each Workspace object into the TabItem using a ContentPresenter (<ContentPresenter Content="{Binding }" />'). WPF normally doesn't know how to draw a Workspace` object, however there is a DataTemplate in the .Resources which tells WPF how to draw the object, so that template is used to render the Workspace object.
  • djangojazz
    djangojazz almost 11 years
    That helps a lot, so in the most basic explanation I would have to load a viewmodel, not a view as I did to my dock in code behind, first and only certain objects accept that type? Dock Control not being one of them. I need to keep at MVVM, it seems like once it is set up right it is easy to add to. But initial setup can be a pain.
  • Rachel
    Rachel almost 11 years
    @djangojazz I think the only way to insert a non-UI object into the UI is to use either a Content property or an ItemsSource property. Content is for single objects, while ItemsSource is for lists of objects. Typically you'll most often see a <ContentControl> object used for single objects, while an <ItemsControl> is the most basic form to insert a collection of objects in the UI.
  • H.B.
    H.B. almost 11 years
    @Rachel: You can also add items directly to the ItemsControl.Items and it will work as well. Further for single objects i use a ContentPresenter more often unless i need the additional properties you have on a ContentControl. (ContentPresenters also conveniently stretch by default).
  • djangojazz
    djangojazz almost 11 years
    That works perfectly. I understanded the concept in your blog and what H B said on seperate of UI and code, what was not getting through to me was the object constraints of DockPanel not letting data through and my implementation of the UI, NOT the viewModel. I was not getting why I could create a view and get some of the stuff, but when I heard child elements of dock doesn't get data, only UI, that made more sense. Once I applied a 'Content Presenter' child element in the parent dock it worked right away when setting the method you stated. Thanks for the help.
  • Rachel
    Rachel almost 11 years
    @H.B. Thanks, I didn't know that :)