locked
How to set ViewModel as data context for Main Page in "On Launched Event"? RRS feed

  • Question

  • Hi,

    I am new to Win Ph 8.1 dev and trying to port a small WPF Mvvm app. I have OnStartUp code in my WPF App.xaml.cs like below.

    protected override void OnStartup(StartupEventArgs e)
            {
                base.OnStartup(e);
    
                ApplicationView app = new ApplicationView();
                ApplicationViewModel context = new ApplicationViewModel();
                app.DataContext = context;
                app.Show();
            }

    And in my Win Ph, On Launched event, my code as below

    MainPage app = new MainPage();
                ApplicationViewModel context = new ApplicationViewModel();
                app.DataContext = context;
                Window.Current.Content = app;
                Window.Current.Activate();
    

    It shows the class name of the viewmodel instead the UI content (as in the attached image).

    Can anyone please guide me here.

    ApplicationViewModel.cs:-

    public class ApplicationViewModel : NotificationBase
        {
            #region Fields
    
            private ICommand _changePageCommand;
    
            private static IPageViewModel _currentPageViewModel;
            private List<IPageViewModel> _pageViewModels;
    
            #endregion
    
            public ApplicationViewModel()
            {
                // Add available pages
                PageViewModels.Add(new CompanyLogoViewModel(this));
                PageViewModels.Add(new GameLogoViewModel(this));
                PageViewModels.Add(new MainMenuViewModel(this));
    
                // Set starting page
                CurrentPageViewModel = PageViewModels[0];
            }
    
            #region Properties / Commands
    
            public ICommand ChangePageCommand
            {
                get
                {
                    if (_changePageCommand == null)
                    {
                        _changePageCommand = new RelayCommand(
                            p => ChangeViewModel((IPageViewModel)p),
                            p => p is IPageViewModel);
                    }
    
                    return _changePageCommand;
                }
            }
    
            public List<IPageViewModel> PageViewModels
            {
                get
                {
                    if (_pageViewModels == null)
                        _pageViewModels = new List<IPageViewModel>();
    
                    return _pageViewModels;
                }
            }
    
            public IPageViewModel CurrentPageViewModel
            {
                get
                {
                    return _currentPageViewModel;
                }
                set
                {
                    if (_currentPageViewModel != value)
                    {
                        _currentPageViewModel = value;
                        NotifyPropertyChanged("CurrentPageViewModel");
                    }
                }
            }
    
            #endregion
    
            #region Methods
    
            private void ChangeViewModel(IPageViewModel viewModel)
            {
                if (!PageViewModels.Contains(viewModel))
                    PageViewModels.Add(viewModel);
    
                CurrentPageViewModel = PageViewModels
                    .FirstOrDefault(vm => vm == viewModel);
            }
    
            #endregion
        }

    MainPage.xaml:-

    <Page
        x:Class="KBFFWinPh.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:KBFFWinPh"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
         xmlns:view="using:KBFFWinPh.Controls"
         xmlns:viewmodel="using:KBFFWinPh.ViewModel">
    
        <Page.Resources>
            <DataTemplate x:Key="viewmodel:CompanyLogoViewModel">
                <view:CompanyLogoControl />
            </DataTemplate>
            <DataTemplate x:Key="viewmodel:GameLogoViewModel">
                <view:GameLogoControl />
            </DataTemplate>
            <DataTemplate x:Key="viewmodel:MainMenuViewModel">
                <view:MainMenuControl />
            </DataTemplate>
        </Page.Resources>
    
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto"  />
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <TextBlock Text="Header content" Grid.Row="0" />
            <ContentControl Content="{Binding CurrentPageViewModel}" Grid.Row="1" />
            <TextBlock Text="footer content" Grid.Row="2" />
        </Grid>
    </Page>

    Friday, September 4, 2015 6:11 PM

Answers

  • A Page is suppposed to be hosted in a Frame. You should navigate to the MainPage in the OnLaunched method and set the DataContext of the Page in its constructor:

    App.xaml.cs:

    protected override void OnLaunched(LaunchActivatedEventArgs e)
            {
                Frame rootFrame = Window.Current.Content as Frame;
    
                if (rootFrame == null)
                {
                    rootFrame = new Frame();
                    Window.Current.Content = rootFrame;
                }
    
                if (rootFrame.Content == null)
                {
                    rootFrame.Navigate(typeof(MainPage), e.Arguments);
                }
                // Ensure the current window is active
                Window.Current.Activate();
            }
    


    MainPage.xaml.cs:

            public MainPage()
            {
                this.InitializeComponent();
         ApplicationViewModel context = new ApplicationViewModel();
                this.DataContext = context:
            }


    This is the recommended approach although you could also set the Content of the Frame to an instance of your Page:

    protected override void OnLaunched(LaunchActivatedEventArgs e)
            {
                Frame rootFrame = Window.Current.Content as Frame;
    
                if (rootFrame == null)
                {
                    rootFrame = new Frame();
                    Window.Current.Content = rootFrame;
                }
    
                if (rootFrame.Content == null)
                {
      MainPage app = new MainPage();
                ApplicationViewModel context = new ApplicationViewModel();
                app.DataContext = context;
                    rootFrame.Content = app;
                }
                // Ensure the current window is active
                Window.Current.Activate();
            }
    

    Hope that helps.

    Please remember to close your threads by marking helpful posts as answer and then please start a new thread if you have a new question. Please don't ask several questions in the same thread.

    • Marked as answer by ykbharat Monday, September 7, 2015 3:52 AM
    Saturday, September 5, 2015 10:43 AM

All replies

  • A Page is suppposed to be hosted in a Frame. You should navigate to the MainPage in the OnLaunched method and set the DataContext of the Page in its constructor:

    App.xaml.cs:

    protected override void OnLaunched(LaunchActivatedEventArgs e)
            {
                Frame rootFrame = Window.Current.Content as Frame;
    
                if (rootFrame == null)
                {
                    rootFrame = new Frame();
                    Window.Current.Content = rootFrame;
                }
    
                if (rootFrame.Content == null)
                {
                    rootFrame.Navigate(typeof(MainPage), e.Arguments);
                }
                // Ensure the current window is active
                Window.Current.Activate();
            }
    


    MainPage.xaml.cs:

            public MainPage()
            {
                this.InitializeComponent();
         ApplicationViewModel context = new ApplicationViewModel();
                this.DataContext = context:
            }


    This is the recommended approach although you could also set the Content of the Frame to an instance of your Page:

    protected override void OnLaunched(LaunchActivatedEventArgs e)
            {
                Frame rootFrame = Window.Current.Content as Frame;
    
                if (rootFrame == null)
                {
                    rootFrame = new Frame();
                    Window.Current.Content = rootFrame;
                }
    
                if (rootFrame.Content == null)
                {
      MainPage app = new MainPage();
                ApplicationViewModel context = new ApplicationViewModel();
                app.DataContext = context;
                    rootFrame.Content = app;
                }
                // Ensure the current window is active
                Window.Current.Activate();
            }
    

    Hope that helps.

    Please remember to close your threads by marking helpful posts as answer and then please start a new thread if you have a new question. Please don't ask several questions in the same thread.

    • Marked as answer by ykbharat Monday, September 7, 2015 3:52 AM
    Saturday, September 5, 2015 10:43 AM
  • I guess the code you had in wpf had a targettype in those datatemplates.

    Something like

    <DataTemplate DataType="local:CompanyLogoViewModel">
         <view:CompanyLogoControl />
    </DataTemplate>As outlined here:
    

    http://stackoverflow.com/questions/10330210/implementing-a-view-model-first-approach-inside-a-parent-view-view-model-using-m

    Then when you set the bound content to a viewmodel, the relevant view is applied as a template.

    Datatemplate doesn't have that datatype property in winrt or wp.

    I suggest you instead make your viewmodels private members of each view and navigate views instead of relying on the templating approach.

    <Page
         .... CompanyLogoView
    
    <Page.DataContext>
      <viewmodel:CompanyLogoViewModel/>
    </Page.DataContext>
    

    You might possibly be able to get it working using a datatemplateselector though.

    When you change the content property of a wpf control it'd force a datatemplateselector to re-evaluate.

    I'm not so sure about wp though.

    You could give it a twirl if you particularly like the viewmodel first approach though.

    https://leeontech.wordpress.com/2012/03/03/using-datatemplateselector-in-metro-style-app/



    • Edited by Andy ONeill Saturday, September 5, 2015 12:55 PM
    Saturday, September 5, 2015 12:50 PM