2

I'm quite a novice to WPF and I'm pretty sure that more experienced WPF coders will be able to spot my problem within seconds.

I'm trying to show a UserControl inside a DataContent, by declaring it as a Template, but all i get instead of it is:

enter image description here

here are the relevant parts of my code:

                            <ContentControl Grid.Column="0" Grid.Row="7" Grid.ColumnSpan="3" >
                             <DataTemplate  DataType="{x:Type ViewModels:anotherViewViewModel}">
                                  <Views:anotherView Content="{Binding}"/>
                             </DataTemplate>
                           </ContentControl>

the View is:

<UserControl x:Class="materialDesignTesting.Views.anotherView"
         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:materialDesignTesting.Views"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
  <Grid Background="Aqua">

  </Grid>
</UserControl>

and the Model View is:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace materialDesignTesting.ViewModels
{
    class anotherViewViewModel
    {
    }
}
Wolfazb
  • 65
  • 6

2 Answers2

2
<ContentControl Grid.Column="0" Grid.Row="7" Grid.ColumnSpan="3" >
    <DataTemplate  DataType="{x:Type ViewModels:anotherViewViewModel}">
          <Views:anotherView Content="{Binding}"/>
    </DataTemplate>
</ContentControl>

Is setting a DataTemplate as the actual content of the content control. You need the ContentTemplate property (or set the resources):

<ContentControl Grid.Column="0" Grid.Row="7" Grid.ColumnSpan="3" >
    <ContentControl.ContentTemplate>
    <DataTemplate  DataType="{x:Type ViewModels:anotherViewViewModel}">
          <Views:anotherView Content="{Binding}"/>
    </DataTemplate>
    </ContentControl.ContentTemplate>
</ContentControl>

Which only works if the Content property of the ContentControl is set to an instance of anotherViewModel. Since its not bound or anything in that code, I'm guessing its still null, and so won't instantiate your view.

You could just use Views:anotherView directly as @Silvermind suggests, but I suspect you are going for some sort of dynamic switching. If that's the case you want to stick with your current approach, just actually give the ContentControl something to display!

Relatedly, unless anotherView is also a ContentControl it probably doesn't have a Content property, so that set/binding is nonsense. anotherView will pick up the instance of anotherViewModel as its DataContext by virtue of being in a data template as you have it, no additional code required.

BradleyDotNET
  • 60,462
  • 10
  • 96
  • 117
1

Usually you would define a template like:

   <DataTemplate DataType="{x:Type local:FooVM}">
       <local:FooUserControl/>
   </DataTemplate>

That goes in a resource dictionary which is merged in app.xaml. You then bind

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

In the viewmodel is the datacontext, ContentProperty is set to an instance of a viewmodel such as FooVM in order to navigate to foo. When you navigate it's best to set ContentProperty to null then your new vm unless you wish to retain view state if the user navigates to the same view.

In any case, when you do this the usercontrol FooUserControl you produce from that template has the content ( your vm ) as it's datacontext.

Here's a simplified example:

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));
        }
    }
}

The view:

<Window x:Class="wpf_Navigation_ViewModelFirst.MainWindow"
    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:wpf_Navigation_ViewModelFirst"
    mc:Ignorable="d"
    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>

Andy
  • 11,864
  • 2
  • 17
  • 20
  • I added the template to the resource dictionary of the MainWindow.Xaml, and created an instance of the FooVM on the MainWindow.Xaml.cs, and binded the content control to the instance, yet the view is still not displayed at all, am I missing something else? – Wolfazb Dec 08 '18 at 11:22
  • 1
    You also need to implement inotifypropertychanged on mainwindow. But I'd usually expect you to be using mainwindowviewmodel. I'll update my answer. – Andy Dec 08 '18 at 12:19
  • Thank you so much, it worked perfectly! My only suggestion is maybe to add an implementation of the RelayCommand in your answer. I used this one: https://stackoverflow.com/questions/48527651/full-implementation-of-relay-command-can-it-be-applied-to-all-cases @Andy – Wolfazb Dec 11 '18 at 09:43