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>
If you want to use implicit
DataTemplates
you have to use the view-model-first approach, you however add a view to yourDockPanel
without a view-model as its context. If you simply add the respective view-model (to aContentControl
orItemsControl
), the view will be created automatically from theDataTemplate
with the view-model as itsDataContext
.e.g.
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.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
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
you are telling the Label to look in its data layer (the
DataContext
) for a property called "HelloString", and use it for theContent
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 yourResourceDictionary
is something different from theDataContext
.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
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 typeDataBoundMVVMChartViewModel
, it should do so by using aDataBoundMVVMChart
.To insert an object into the UI, a
Content
property is normally used, such asor
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.
What is this "DataContext" you speak of?
A simple MVVM example
As for your actual question:
I prefer using a
DataTemplate
in the.Resources
somewhere, as then your UI is not specifically tied with one specificDataContext
.This is especially important when creating re-usable controls with MVVM. For example, if you have a
CalculatorUserControl
, and you assigned it aDataContext
in the control itself such as with<UserControl.DataContext>
, then you could never use thatCalculatorUserControl
with any otherDataContext
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 useDataTemplates
to tell WPF how to draw the differentViewModels
orModels
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)