2
\$\begingroup\$

I am looking for some advice on how to create a new ViewModel within an already existing ViewModel.

I am creating a Tic Tac Toe game, to improve my knowledge of the MVVM pattern, WPF and C#. I am looking to contain the buttons (TileViewModel) as separate ViewModels and implement these into the game window (GameViewModel). I have made a list of TileViewModels in the GameViewModel and added 9 of these views to the XAML, binding the list in the ViewModel with each View using DataContext with the index of the array.

GameViewModel.cs

using MvvmCross.Commands;
using MvvmCross.ViewModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using TicTacToe.Core.Models;

namespace TicTacToe.Core.ViewModels
{
    public class GameViewModel : MvxViewModel, IBaseViewModel
    {
        private string nextValue = "X";
        private bool _won;
        public List<TileViewModel> ViewTiles { get; set; }

        public GameViewModel() {
            Grid grid = new Grid();
            grid.Start();

            ViewTiles = new List<TileViewModel> { 
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this),
                new TileViewModel(this)
            };
        }

        public string GetNextValue()
        {
            // Get the next value to display on the TileViewModel
            nextValue = nextValue.Equals("X") ? "O" : "X";

            return nextValue;
        }

        public bool Won
        {
            get { return _won; }
            set
            {

                _won = value;
            }
        }

        private void blockWon()
        {

        }

        public bool CheckForWin()
        {
            int AddValue(string tileValue)
            {
                if (tileValue.Equals("X"))
                {
                    return 1;
                }
                else if(tileValue.Equals("O"))
                {
                    return -1;
                }
                return 0;
            }

            // check rows
            int total = 0;
            for (int i=0; i<ViewTiles.Count; i++)
            {
                if (i % 3 == 0)
                {
                    if (Math.Abs(total) == 3)
                    {
                        Won = true;
                    }
                    total = 0;
                }

                total += AddValue(ViewTiles[i].Value);
            }

            // check columns
            total = 0;
            int loop_count = 0;
            for (int i=0; i<ViewTiles.Count; i++)
            {
                if (i % 3 == 0 && i != 0)
                {
                    if (Math.Abs(total) == 3)
                    {
                        Won = true;
                    }
                    loop_count++;
                    total = 0;
                }

                total += AddValue(ViewTiles[((i % 3) * 3) + loop_count].Value);
            }

            return false;
        }
    }
}

GameView.xaml

<views:MvxWpfView
             xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
             xmlns:customviews="clr-namespace:TicTacToe.Wpf.Views"
             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:TicTacToe.Wpf.Views"
             xmlns:mvx="clr-namespace:MvvmCross.Platforms.Wpf.Binding;assembly=MvvmCross.Platforms.Wpf" xmlns:ViewModels="clr-namespace:TicTacToe.Core.ViewModels;assembly=TicTacToe.Core" x:Class="TicTacToe.Wpf.Views.GameView"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <customviews:TileView DataContext="{Binding ViewTiles[0]}" Grid.Column="0" Grid.Row="0"/>
        <customviews:TileView DataContext="{Binding ViewTiles[1]}" Grid.Column="1" Grid.Row="0"/>
        <customviews:TileView DataContext="{Binding ViewTiles[2]}" Grid.Column="2" Grid.Row="0"/>
        <customviews:TileView DataContext="{Binding ViewTiles[3]}" Grid.Column="0" Grid.Row="1"/>
        <customviews:TileView DataContext="{Binding ViewTiles[4]}" Grid.Column="1" Grid.Row="1"/>
        <customviews:TileView DataContext="{Binding ViewTiles[5]}" Grid.Column="2" Grid.Row="1"/>
        <customviews:TileView DataContext="{Binding ViewTiles[6]}" Grid.Column="0" Grid.Row="2"/>
        <customviews:TileView DataContext="{Binding ViewTiles[7]}" Grid.Column="1" Grid.Row="2"/>
        <customviews:TileView DataContext="{Binding ViewTiles[8]}" Grid.Column="2" Grid.Row="2"/>
    </Grid>
</views:MvxWpfView>

TielViewModel.cs

using MvvmCross.ViewModels;
using MvvmCross.Commands;
using System;
using System.Collections.Generic;
using System.Text;

namespace TicTacToe.Core.ViewModels
{
    public class TileViewModel : MvxViewModel
    {
        private string _value = "";
        private bool _enabled = true;
        private IBaseViewModel _baseViewModel;
        public IMvxCommand TileClick { get; set; }

        public TileViewModel(IBaseViewModel baseViewModel)
        {
            TileClick = new MvxCommand(Increment);

            _baseViewModel = baseViewModel;
        }

        public string Value {
            get { return _value;  } 
            set
            {
                _value = value;
                RaisePropertyChanged(() => Value);
            }
        }

        public bool Enabled
        {
            get { return _enabled; }
            set
            {
                _enabled = value;
                RaisePropertyChanged(() => Enabled);
            }
        }

        public void Increment()
        {
            Enabled = false;
            Value = _baseViewModel.GetNextValue();
            _baseViewModel.CheckForWin();
        }
    }
}

TileView.xaml

<views:MvxWpfView x:Class="TicTacToe.Wpf.Views.TileView"
             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:TicTacToe.Wpf.Views"
             mc:Ignorable="d"
             xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
             xmlns:mvx="clr-namespace:MvvmCross.Platforms.Wpf.Binding;assembly=MvvmCross.Platforms.Wpf"
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Button mvx:Bi.nd="Command TileClick" IsEnabled="{Binding Enabled}">
            <TextBlock Text="{Binding Value}"/>
        </Button>
    </Grid>
</views:MvxWpfView>

The solution I have implemented works well, I was just wondering if there was a better way to go about implementing a solution like this.

I am specifically using MVVMCross, but I guess this is more of a generic MVVM solution.

Thanks!

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Just for reference: recently i wrote an answer for similar game in WPF+MVVM but in Russian StackOverflow Community. If you want, you may look at the code, here's link. There's implementation for Game field only, without internal game logic. 8x8 there but change it to 3x3 is job to replace 2 contants. :) Especially look, how I'm using ItemsControl there. Also there's no any MVVM framework, only pure WPF. \$\endgroup\$
    – aepot
    Feb 9 at 20:36
  • 1
    \$\begingroup\$ Here's a Linq trick to create 9 tiles ViewTiles = Enumarable.Range(0, 9).Select(_ => new TileViewModel(this)).ToList(). 9x customviews:TileView in Grid can be replaced with one UniformGrid \$\endgroup\$
    – aepot
    Feb 9 at 20:45
  • 1
    \$\begingroup\$ @aepot Thank you for the Linq hints, and the reference to the other question, I have tried to implement your code on the XAML but get the error The property 'ItemsSource' was not found in type 'UniformGrid', any ideas on this, my XAML knowledge is very basic. Is having nested ViewModels like this a correct approach for my application? Thanks again, \$\endgroup\$
    – Jack Dane
    Feb 9 at 21:00
  • 1
    \$\begingroup\$ Ah, sorry, forgot some important things <ItemsControl ItemsSource="{Binding ViewTiles}"><ItemsControl.ItemTemplate><DataTemplate><customviews:TileView DataContext="{Binding}"/></DataTemplate></ItemsControl.ItemTemplate><ItemsControl.ItemsPanel><ItemsPanelTemplate><UniformGrid Rows="3" Columns="3"/></ItemsPanelTemplate></ItemsControl.ItemsPanel></ItemsControl> \$\endgroup\$
    – aepot
    Feb 9 at 21:20
  • 1
    \$\begingroup\$ @aepot Thank you so much! That was exactly what I was looking for originally! I didn't like the hardcoding on the View! \$\endgroup\$
    – Jack Dane
    Feb 9 at 21:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.