I’m trying to setup an MVVM style application, and I think I’m getting myself into a few knots with the interactions of these items and hoping someone can help. Am I doing something really wrong here?
I suppose my main 2 questions are
- How should I be going from my model to my view. Currently I’m trying to do this via a converter.
- If using a converter is correct, how do I get this working correctly? I believe the datacontext set on the Node constructor is replaced by the XAML that includes the converter.
My classes (simplified a bit):
IFieldDescription
// Interface that is supposed to be the Model
public interface IFieldDescription
{
String Name { get; }
bool Disabled { get; }
}
NodeModel
// Class that is supposed to be the ViewModel
public class NodeModel : NotifyPropertyChanged
{
internal NodeModel() { }
public NodeModel(IFieldDescription fieldDescription)
{
this.FieldDescription = fieldDescription;
}
protected IFieldDescription FieldDescription
{
get { return this.fieldDescription; }
set {
this.fieldDescription = value;
this.OnPropertyChanged("Name");
this.OnPropertyChanged("Disabled");
this.OnPropertyChanged("PrimaryKey"); }
}
private IFieldDescription fieldDescription;
public String Name { get { return this.FieldDescription.Name; } }
public Boolean Disabled { get { return this.FieldDescription.Disabled; } }
}
Node
Code behind
public Node(NodeModel model)
{
this.DataContext = model;
this.InitializeComponent();
}
XAML
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GO" x:Class="GO.Node" Background="White"
>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/> <!-- Primary Key Icon -->
<ColumnDefinition Width="Auto"/> <!-- Type Icon -->
<ColumnDefinition/> <!-- Node Text -->
<ColumnDefinition Width="Auto"/> <!-- Option Cog -->
<ColumnDefinition Width="Auto"/> <!-- Match Icon -->
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<local:AttributeDataTypeConverter x:Key="DateTypeConverter"/>
</Grid.Resources>
<Image Grid.Column="0" Source="C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\Images\PrimaryKey.png" Stretch="None" Visibility="{Binding Path=IsPrimaryKey}"/>
<Image Grid.Column="1" Source="{Binding Path=Type, Converter={StaticResource DateTypeConverter}}" Stretch="None"/>
<TextBlock Grid.Column="2" Text="{Binding Path=Name}" VerticalAlignment="Bottom" Margin="0,0,0,2"/>
<Image Grid.Column="3" Source="C:\Users\ian.wright\Documents\Expression\Blend 4\Projects\GO\GO\Resources\Images\Cog.png" Stretch="None" Visibility="{Binding Path=HasOptions}"/>
<Image Grid.Column="4" Source="{Binding Path=CastType}" Stretch="None"/>
</Grid>
</UserControl>
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="GO.MainWindow"
xmlns:local="clr-namespace:GO"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid Width="200" Height="500">
<Grid.Resources>
<local:NodeConverter x:Key="NodeConverter"/>
<local:ModelToViewConverter x:Key="ModelConverter"/>
</Grid.Resources>
<!--<ListView Grid.Column="1" ItemsSource="{Binding Path=FieldDescriptions, Converter={StaticResource ModelConverter}}">-->
<ListView Grid.Column="1" ItemsSource="{Binding Path=FieldDescriptions}">
<ListView.ItemTemplate>
<DataTemplate>
<local:Node DataContext="{Binding Converter={StaticResource ModelConverter}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
The data context for the mainwindow is set to a new RD() which is defined below:
RD
public class RD
{
private IEnumerable<IFieldDescription> GetTestData()
{
yield return new FieldDescription("String", true);
yield return new FieldDescription("Integer", false);
yield return new FieldDescription("Double", false);
yield return new FieldDescription("Date", false);
yield return new FieldDescription("Enum", false);
}
public virtual ObservableCollection<IFieldDescription> FieldDescriptions
{
get { return new ObservableCollection<IFieldDescription>(GetTestData()); }
}
}
Any my converter is currently defined as :
public class ModelToViewConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return null;
if (value is IFieldDescription)
{
NodeModel model = new NodeModel((IFieldDescription)value);
return new Node(model);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usually I use
DataTemplatesfor tying aViewwith aModelorViewModelThe only place my View’s code-behind ever references a Model or ViewModel is on startup when it sets the startup ViewModel as the startup View’s
DataContext. Everything else is hooked up withDataTemplates(orDataTemplateSelectorsfor Silverlight).(Actually to be fair sometimes I do need to do something special and I will cast an object’s
DataContextas a ViewModel or Model in the code-behind, but those cases are rare and I view them as hacks)For example, setting the startup View/ViewModel:
Here’s an example of some DataTemplates:
And finally, I’ll use
ContentControlsin my XAML where I want to display my Viewsor
Sometimes ContentControls are not even needed. For example, if you bound a
ListViewto anObservableCollection<NodeModel>, then each item in theListViewwould be an object of typeNodeModeland WPF will automatically pick up the DataTemplate for that.The idea behind MVVM is that your entire application functions in your ViewModels, and the Views are simply a pretty UI which sits on top of the ViewModels to make them more user-friendly. In a perfect world, the View could easily be replaced by any other UI
If you’re interested, I have a simple MVVM example on my blog which contains some examples of using the MVVM design pattern