Koenyn
Koenyn

Reputation: 704

Binding ContentControl to a View for a ViewModel

I'm using MVVM in a windows phone 8 application. I would like to move from 1 view model to another inside my shell view model. I can't seem to get the ContentControl to bind to a template that is a usercontrol/phoneApplicationPage over the view model.

What am I missing?

I'm trying to avoid things like MVVM light. (I want my app to be as small a download as possible) And this should be possible to do.

P.S. I'm still pretty new to WPF/WP8

Here is a sample of what I have so far, Excuse the dumb functionality :)

/** The Shell view **/

<phone:PhoneApplicationPage
    x:Class="PhoneAppWithDataContext.Navigation.ViewModelNavigation"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    mc:Ignorable="d"
    shell:SystemTray.IsVisible="True"
    xmlns:vm="clr-namespace:PhoneAppWithDataContext.Navigation">

    <phone:PhoneApplicationPage.DataContext>
        <vm:AppViewModel/>
    </phone:PhoneApplicationPage.DataContext>
    <phone:PhoneApplicationPage.Resources>
        <DataTemplate x:Key="MonthViewModel">
            <vm:MonthViewControl/>
        </DataTemplate>
    </phone:PhoneApplicationPage.Resources>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <ContentControl Content="{Binding CurrentViewModel}"
                ContentTemplate="{Binding ContentTemplate}"
                HorizontalContentAlignment="Stretch"
                VerticalContentAlignment="Stretch"/>
        </Grid>
        <Button Content="Change VM" Command="{Binding ChangeViewModelCommand}"/>
    </Grid>
</phone:PhoneApplicationPage>

/** Shell/Application View Model **/

public class AppViewModel : ViewModelBase
{
    private ViewModelBase _currentViewModel;
    private List<ViewModelBase> _viewModels = new List<ViewModelBase>();
    private byte _month = 1;

    public ViewModelBase CurrentViewModel
    {
        get
        {
            return _currentViewModel;
        }
        set
        {
            if (_currentViewModel == value)
                return;

            _currentViewModel = value;
            NotifyPropertyChanged("CurrentViewModel");
        }
    }

    public DataTemplate SelectedTemplate
    {
        get
        {
            if (_currentViewModel == null)
                return null;
            return DataTemplateSelector.GetTemplate(_currentViewModel);
        }
    }

    public List<ViewModelBase> ViewModels
    {
        get
        {
            return _viewModels;
        }
    }

    public AppViewModel()
    {
        ViewModels.Add(new MonthViewModel(_month));
        CurrentViewModel = ViewModels.FirstOrDefault();
    }

    private ICommand _changeViewModelCommand;
    public ICommand ChangeViewModelCommand
    {
        get
        {
            return _changeViewModelCommand ?? (_changeViewModelCommand = new GenericCommand(() =>
            {
                _month++;
                var newVM = new MonthViewModel(_month);
                ViewModels.Add(newVM);
                CurrentViewModel = newVM;

            }, true));
        }
    }
    private void ChangeViewModel(ViewModelBase viewModel)
    {
        if (!ViewModels.Contains(viewModel))
            ViewModels.Add(viewModel);

        CurrentViewModel = ViewModels.FirstOrDefault(vm => vm == viewModel);
    }
}

/** DataTemplateSelector **/

public static class DataTemplateSelector
{
    public static DataTemplate GetTemplate(ViewModelBase param)
    {
        Type t = param.GetType();
        return App.Current.Resources[t.Name] as DataTemplate;
    }
}

/** User Control **/

<UserControl x:Class="PhoneAppWithDataContext.Navigation.MonthViewControl"
    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"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    d:DesignHeight="480" d:DesignWidth="480"
    xmlns:vm="clr-namespace:PhoneAppWithDataContext.Navigation">


    <UserControl.DataContext>
        <vm:MonthViewModel/>
    </UserControl.DataContext>

    <Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="Id" Width="100" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
        <TextBlock Text="{Binding Id}" Width="100" VerticalAlignment="Center" Grid.Row="0" Grid.Column="1" />
        <TextBlock Text="Name" Width="100" VerticalAlignment="Center" Grid.Row="1" Grid.Column="0" />
        <TextBlock Text="{Binding Name}" Width="100" VerticalAlignment="Center" Grid.Row="1" Grid.Column="1" />
    </Grid>
</UserControl>

/** ViewModelBase **/

public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(String propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

/** View model that the user control should bind to **/

public sealed class MonthViewModel : ViewModelBase
{
    private byte _id;
    private string _name;

    public MonthViewModel()
    {
    }

    public MonthViewModel(byte id)
    {
        _id = id;
        _name = "Month " + id.ToString() + " of the year";
    }

    public override string ToString()
    {
        return _name;
    }

    public byte Id
    {
        get
        {
            return _id;
        }
    }
    public string Name
    {
        get
        {
            return _name;
        }
    }
}

Upvotes: 0

Views: 2947

Answers (1)

Kcvin
Kcvin

Reputation: 5163

I believe the problem here is:

<UserControl.DataContext>
    <vm:MonthViewModel/>
</UserControl.DataContext>

When your Content is changed from one MonthViewModel to the next, the DataContext of the returned DataTemplate is set to the object bound to Content. Well, once that DataContext is set, you should be good to go, but once the UserControl is loaded, it is resetting the DataContext to a new instace of an empty MonthViewModel (vm:MonthViewModel). Get rid of that explicit DataContext declaration--in other words delete the code that I posted above.

That way, when you first call CurrentViewModel and INPC is raised, it won't reset the DataContext. When you switch between CurrentViewModel's that are of MonthViewModel type, your UserControl won't call InitializeComponent again, instead the DataContext will change.

EDIT

In addition, if you still aren't seeing changes, then I would point to SelectedTemplate property. Instead of the null check in the property, just pass null to the GetTemplate. Inside of GetTemplate, check for null and return null there if it is null.

Upvotes: 3

Related Questions