Wpf Mvvm Why Use Contentcontrol + Datatemplate Views Rather Than Straight Xaml Window Views

WPF MVVM Why use ContentControl + DataTemplate Views rather than straight XAML Window Views?

People use DataTemplates that way when they want to dynamically switch Views depending on the ViewModel:

<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<!-- View 1 Here -->
</DataTemplate>

<DataTemplate DataType="{x:Type local:VM2}">
<!-- View 2 here -->
</DataTemplate>
</Window.Resources>

<ContentPresenter Content="{Binding}"/>

</Window>

So,

if Window.DataContext is an instance of VM1, then View1 will be displayed,

and if

Window.DataContext is an instance of VM2, then View2 will be displayed.

Granted, it makes no sense at all if only 1 View is expected, and never changed.

ContentControl with DataTemplate is not displaying anything (WPF MVVM)

Frame is a bit special. Normally, child controls inherit the DataContext of their parent. However, with a Frame, the children do not inherit the DataContext. As a result, your ContentControl has a DataContext of null.

To verify this, give your ContentControl a name like the following:

<ContentControl x:Name="MyContentControl" Content="{Binding TestViewContext}">

Then in the constructor of your MainView, check the DataContext as follows:

public MainView()
{
// Other code

// Set a breakpoint here and view the DataContext
var dataContext = MyContentControl.DataContext;
}

For further reading, you could read the following post:
page.DataContext not inherited from parent Frame?

Also, as a side note, Frame intended use was setting the Source property to an external file. As you may have noticed, in order to set child content in xaml, you need to specify <Frame.Content> unlike other controls.

Using ContentControl to display views in WPF doesn't work

I have finally found what the problem was. For some reason, when you declare x:Key on the DataTemplate, it made the ContentControl Content appear as a string. I have removed the x:Key of the DataTemplate and everything works now. I also had to apply nemesv's answer for it to work.

Before:

<DataTemplate x:Key="LoginView" DataType="vm:LoginViewModel">

After (resolves the problem):

<DataTemplate DataType="{x:Type vm:LoginViewModel}">

WPF / MVVM : Need help to fix a broken binding with ContentControl + DataTemplate

I have found a solution to my problem :)

I wrote a DataTemplate for each shape that I want to display with the DataTrigger (I set it as ContentTemplate), and no more error message in the Output Window :

<Window.Resources>
<DataTemplate x:Key="LogDataTemplate">
<StackPanel Orientation="Horizontal">

<ContentControl Name="Indicator" Width="8" Height="8" Margin="0,0,5,0" HorizontalAlignment="Center"
>
<ContentControl.Resources>
<DataTemplate x:Key="TemplateError">
<ContentControl Content="{StaticResource Triangle}" Foreground="{StaticResource BaseRed}"/>
</DataTemplate>
<DataTemplate x:Key="TemplateWarning">
<ContentControl Content="{StaticResource Triangle}" Foreground="{StaticResource BaseYellow}"/>
</DataTemplate>
<DataTemplate x:Key="TemplateInformation">
<ContentControl Content="{StaticResource CircleFull}" Foreground="{StaticResource BaseGreen}"/>
</DataTemplate>
<DataTemplate x:Key="TemplateDefault">
<ContentControl Content="{StaticResource CircleBorderOnly}" Foreground="Gray"/>
</DataTemplate>
<DataTemplate x:Key="TemplateNull"/>
</ContentControl.Resources>

<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateDefault}" />
<Style.Triggers>
<DataTrigger Binding="{Binding sMessageType}" Value="Error">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateError}" />
<Setter Property="LayoutTransform">
<Setter.Value>
<RotateTransform Angle="180"/>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding sMessageType}" Value="Information">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateInformation}" />
</DataTrigger>
<DataTrigger Binding="{Binding sMessageType}" Value="Warning">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateWarning}" />
</DataTrigger>
<!-- Used to avoid to display a gray circle when nothing to display -->
<DataTrigger Binding="{Binding sMessageType}" Value="{x:Null}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateNull}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

<TextBlock Text="{Binding sMessage}" Style="{StaticResource DefaultLogTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</Window.Resources>

Associating a ViewModel to a View

It turns out to use the following code likely.

<ContentControl Content="{Binding MyViewInstance}">
<ContentControl.Resources>
<DataTemplate DataType="x:Type vm:MyViewModel">
</DataTemplate>
</ContentControl.Resources>
</ContentControl>

WPF MVVM how to set initial value of contentcontrol

As mentioned in comments, CurrentView should really be CurrentViewModel if you want to do viewmodel first. Otherwise those templates are a bit pointless.

Here's something I put together a while back.
MainWindow:

    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:LoginViewModel}">
<local:LoginUC/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UserViewModel}">
<local:UserUC/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel>
<Button Content="Login Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:LoginViewModel}"
/>
<Button Content="User Page"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type local:UserViewModel}"
/>
</StackPanel>
<ContentControl Grid.Column="1"
Content="{Binding CurrentViewModel}"
/>
</Grid>

Notice that I have one command which takes a Type as a parameter.

There are templates for those two types which give you the views.

Oh.... and I called my property CurrentViewModel. Because it's going to present an instance of one of those viewmodels to the content of my contentcontrol.

ViewModel:

public class MainWindowViewModel : INotifyPropertyChanged
{
private object currentViewModel;

public object CurrentViewModel
{
get { return currentViewModel; }
set { currentViewModel = value; RaisePropertyChanged(); }
}
private RelayCommand<Type> navigateCommand;
public RelayCommand<Type> NavigateCommand
{
get
{
return navigateCommand
?? (navigateCommand = new RelayCommand<Type>(
vmType =>
{
CurrentViewModel = null;
CurrentViewModel = Activator.CreateInstance(vmType);
}));
}
}

public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

Setting the property to null first guarantees you don't retain any state in a view. I take the type which is passed in from the command and create an instance of that viewmodel. It's then templated out by the appropriate datatemplate. This is a particularly simple illustrative sample only and one obvious improvement would be to retain state of each viewmodel using a dictionary keyed on type. So you instantiate any given viewmodel type just the once in a session.

That is viewmodel first.

Some people prefer view first. And of course if you wanted a new window each time then maybe that's the direction you want to head in. View first is arguably a view responsibility and you could handle instantiating a new view in the view itself. If you wanted. That could be done using a routed event and parameter.

Which could look like:

    <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox HorizontalContentAlignment="Stretch"
Button.Click="Button_Click"
>
<Button Content="Home" Tag="{x:Type local:HomeView}"/>
<Button Content="Departments" Tag="{x:Type local:DepartmentView}"/>
</ListBox>
<ContentControl Name="NavigationParent"
Grid.Column="1"/>
</Grid>

This version retains state of each view. The view would instantiate it's own viewmodel in this pattern.

public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Navigate(typeof(HomeView));
}

private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn = (Button)e.OriginalSource;
Navigate(btn.Tag as Type);
}

private void Navigate (Type viewType)
{
UserControl uc;
if (Views.ContainsKey(viewType))
{
uc = Views[viewType];
}
else
{
uc = (UserControl)Activator.CreateInstance(viewType);
Views[viewType] = uc;
}
NavigationParent.Content = uc;
}
private Dictionary<Type, UserControl> Views = new Dictionary<Type, UserControl>();
}


Related Topics



Leave a reply



Submit