HateDread
Topic Author
Posts: 74
Joined: 06 Feb 2020, 10:27

Detroit Sample: how do I reset ViewModels on state change? Where should C++ VMs come from?

11 Dec 2022, 16:41

I've implemented my menu screens by following the Detroit example; with VisualStateManager.VisualStateGroups -> and one VisualState per menu screen, which hide/unhide the different views (which can have their own ViewModel), ala
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="States">
                
                <VisualState x:Name="Home">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="HomeScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="StartScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="SettingsScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

                <VisualState x:Name="Start">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="HomeScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="StartScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="SettingsScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

                <VisualState x:Name="Settings">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="HomeScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="StartScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
                        </ObjectAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="SettingsScreen">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>

            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
The issue is that one of these has a ViewModel with state in it, and I'd like that state to be reset on each return. (In this case, it's the pre-game setup, i.e. a game a lobby, with per-session settings that shouldn't persist if you back out to the main menu and then come back to the lobby screen). How should I go about that in this setup?

-----------------------

Second question; if I'm trying to control the menu screens in Xaml using these visual states, how do/should I create and manage my ViewModel C++ classes? I suppose I can have the xaml/Noesis create them when the view is instantiated, but what if those VMs need to communicate with the rest of the game? Right now, for my current VMs, they're all created from C++ manually with constructors that give them references to various engine systems, but this would be an inversion of that so I'm a bit lost on that part of the concept. To be fair, the existing VMs are essentially; are we in the menu? Create Menu VM. Are we entering gameplay? Create Game VM. Whereas this menu switching is more complicated! But I am enjoying the VisualState switching concept from that sample, these issues aside :)

Thanks.
 
HateDread
Topic Author
Posts: 74
Joined: 06 Feb 2020, 10:27

Re: Detroit Sample: how do I reset ViewModels on state change? Where should C++ VMs come from?

12 Dec 2022, 11:54

I think I should clarify the above late-night message, specifically question 2.

When I know we're loading into the menu, I manually create my MenuViewModel and assign it to a "CurrentViewModel" property on a MainViewModel class. That main view model is just to hold whatever the "main" view model is at that point, and whichever view model is set has its xaml loaded.

MainWindow.xaml
<Grid
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:rtg"
    xmlns:local-views="clr-namespace:rtg.Views" Background="Transparent">
    <Grid.Resources>
        <DataTemplate DataType="{x:Type MenuViewModel}">
            <local-views:MenuView/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type GameViewModel}">
            <local-views:GameView/>
        </DataTemplate>
    </Grid.Resources>
	<ContentControl Content="{Binding Path=CurrentViewModel}"></ContentControl>
</Grid>
Similarly, when we know the gameplay is loaded, we create a GameViewModel and set it as the "CurrentViewModel" above, replacing the menu view model. This all works well, but it revolves around us having a "main" view model, and then we create other view models within those as needed (e.g. a viewmodel for each unit that is created in the game, which exist on the GameViewModel directly), and we bind to those from various views.

If I let Noesis create my view models for me as per the original post, how do I update its properties etc since I don't have any pointers/references to it? They exist out in space and I have no direct connection to them. Conversely, if I revert to manually creating the view models am I not taking back over control from the viewstate switching? I'm trying to avoid too much state handling in C++.

My game lobby screen view xaml's start - can see it creating the view model.
<UserControl x:Class="rtg.Views.MenuGameSetupView"
             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:rtg.Views" xmlns:viewmodels="clr-namespace:rtg.ViewModels"
             xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:views="clr-namespace:rtg.Views"
             mc:Ignorable="d"
             d:DesignHeight="1440" d:DesignWidth="2440"
             x:Name="MenuGameSetupView">
    <UserControl.DataContext>
        <viewmodels:MenuGameSetupViewModel></viewmodels:MenuGameSetupViewModel>
    </UserControl.DataContext>
 
HateDread
Topic Author
Posts: 74
Joined: 06 Feb 2020, 10:27

Re: Detroit Sample: how do I reset ViewModels on state change? Where should C++ VMs come from?

12 Dec 2022, 15:28

Sorry for the triple post - but I think I found a way to do this.

In the main MenuView.xaml, I create the sub-views for each menu screen like so
        <local:MenuGameSetupView x:Name="StartScreen" Visibility="Collapsed" DataContext="{Binding MenuGameSetupViewModel}"/>
And on the MenuViewModel, I create and reference a MenuGameSetupViewModel and expose as a property. This way, I control it and can link it to engine systems etc. Now in these sub-views I can use the bound view model as the DataContext.

---------

As to question 1; resetting the view model;

In the code-behind, we need to hook the visibility changed event
    public partial class MenuGameSetupView : UserControl
    {
        public MenuGameSetupView()
        {
            InitializeComponent();

            IsVisibleChanged += MenuGameSetupView_IsVisibleChanged;
        }

        private void MenuGameSetupView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (RouteOnVisibilityChanged != null)
            {
                RouteOnVisibilityChanged.Execute(sender);
            }
        }

        public static readonly DependencyProperty RouteOnVisibilityChangedProperty =
            DependencyProperty.Register(
                "RouteOnVisibilityChanged",
                typeof(ICommand),
                typeof(MenuGameSetupView),
                new UIPropertyMetadata(null));

        public ICommand RouteOnVisibilityChanged
        {
            get { return (ICommand)GetValue(RouteOnVisibilityChangedProperty); }
            set { SetValue(RouteOnVisibilityChangedProperty, value); }
        }
    }
We create a function in the high-level menu view model to clear this sub view model.
        public MenuViewModel()
        {
            NewGameSessionRequired = new DelegateCommand(o => OnNewGameSessionRequired(o));

            GameSetupViewModel = new MenuGameSetupViewModel();
            ServerBrowserViewModel = new ServerBrowserViewModel();
        }
        private void OnNewGameSessionRequired(object sender)
        {
            GameSetupViewModel = new MenuGameSetupViewModel();
        }
        
        //...       
And we update the earlier xaml to hook the sub-view's visibility change callback to this game session recreation
        <local:MenuGameSetupView x:Name="StartScreen" Visibility="Collapsed" RouteOnVisibilityChanged="{Binding Path=DataContext.NewGameSessionRequired, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type views:MenuView}}}" DataContext="{Binding GameSetupViewModel}"/>
---------------------

Do these approaches make sense? Any better ideas / feedback? :)
 
User avatar
sfernandez
Site Admin
Posts: 3363
Joined: 22 Dec 2011, 19:20

Re: Detroit Sample: how do I reset ViewModels on state change? Where should C++ VMs come from?

16 Dec 2022, 18:33

It looks fine to me. The general idea when using MVVM is that you can have a hierarchy of ViewModels that you can manage, and then assign them to the different parts of your UI. So a specific component only interacts with its own ViewModel, while a parent view model can decide if it needs to recreate it or not.

Who is online

Users browsing this forum: Google [Bot] and 9 guests