Lesson 05.2: Creating the Player Inventory

Now that we can create items, it’s time to create the player’s inventory. In this lesson, we will add an Inventory property to the Player class, and display the inventory items in the UI.

 

 

Summary

 

Step 1: Edit Engine\Player.cs

The first thing we’ll do is create a new property in the Player class, to hold the GameItems the Player will have in their inventory.

In the ItemFactory class, we used a List<GameItem> variable to hold multiple GameItem objects. For the Player’s Inventory, we are going to use an ObservableCollection<GameItem> property. Collections are similar to Lists, and can hold zero, one, or many objects. However, an ObservableCollection also automatically notifies the UI when objects are added to it, or removed from it. Because of this, we don’t need to call “OnPropertyChanged” every time we modify the item’s in the player’s inventory.

 

To use ObservableCollection, we need to add “using System.Collections.ObjectModel;”

 

We also need to initialize the property (like we do for all list/collection properties). We do this by creating a constructor for the Player class, and initializing the property to a “new ObservableCollection<GameItem>()”. Now, when a Player object is created, the Inventory property will be initialized, and the player can start receiving items.

 

Player.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Engine.Models
{
    public class Player : BaseNotificationClass
    {
        private string _name;
        private string _characterClass;
        private int _hitPoints;
        private int _experiencePoints;
        private int _level;
        private int _gold;

        public string Name
        {
            get { return _name; }
            set
            {
                _name = value; 
                OnPropertyChanged(nameof(Name));
            }
        }

        public string CharacterClass
        {
            get { return _characterClass; }
            set
            {
                _characterClass = value; 
                OnPropertyChanged(nameof(CharacterClass));
            }
        }

        public int HitPoints
        {
            get { return _hitPoints; }
            set
            {
                _hitPoints = value; 
                OnPropertyChanged(nameof(HitPoints));
            }
        }

        public int ExperiencePoints
        {
            get { return _experiencePoints; }
            set
            {
                _experiencePoints = value; 
                OnPropertyChanged(nameof(ExperiencePoints));
            }
        }

        public int Level
        {
            get { return _level; }
            set
            {
                _level = value; 
                OnPropertyChanged(nameof(Level));
            }
        }

        public int Gold
        {
            get { return _gold; }
            set
            {
                _gold = value;
                OnPropertyChanged(nameof(Gold));
            }
        }

        public ObservableCollection<GameItem> Inventory { get; set; }

        public Player()
        {
            Inventory = new ObservableCollection<GameItem>();
        }
    }
}

 

Step 2: Edit WPFUI\MainWindow.cs

Currently, we have a Label that says “Inventory/Quests”, for Grid.Row=”2″ and Grid.Column=”0″ (around line 102, in MainWindow.xaml). We are going to replace it with a Grid.

Inside the Grid, we add a new type of UI control – a TabControl. This is a control that looks like a paper file folders (like you would store in a desk drawer), with tabs at different positions.

 

 

Inside the TabControl, you create a TabItem for each “tab”. The TabItem’s Header attribute is the text to display.

 

Inside the TabItem, we will add a DataGrid. This is a popular control for displaying information that has multiple rows (like lists and collections) and multiple columns (in this case, properties we want to display for the objects in the list/collection).

The DataGrid’s ItemsSource attribute is the list/collection to display. We also want to set the “AutoGenerateColumns” attribute to “False”. By default, a datagrid will create a column for each property of the object in the list/collection. However, we want to select the properties to display, and how we want to display them. Finally, we want to set the DataGrid’s “HeadersVisibility” attribute to “Column”. So it will only show column name headers, and not row headers.

Between the DataGrid opening and closing tags, we need the tags for “DataGrid.Columns”, since this is where we will define the details for the columns.

We use two DataGridTextColumns, because we want to display text values. There are other columns types you can use to display checkboxes (DataGridCheckBoxColumn), dropdown boxes (DataGridComboBoxColumn), text links (DataGridHyperlinkColumn), and customized content (DataGridTemplateColumn). But, for this DataGrid, we only need to use DataGridTextColumn.

For the attributes of the DataGridTextColumn, we set the Header (the text to display at the top of the column), the property to bind to (from the GameItem objects, in the DataGrid’s ItemsSource), and the width.

 

MainWindow.xaml

<Window x:Class="WPFUI.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:WPFUI"
        mc:Ignorable="d"
        FontSize="11pt"
        Title="Scott's Awesome Game" Height="768" Width="1024">

    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="225"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Content="Menu" Background="AliceBlue"/>

        <Grid Grid.Row="1" Grid.Column="0" Background="Aquamarine">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            
            <Label Grid.Row="0" Grid.Column="0" Content="Name:"/>
            <Label Grid.Row="0" Grid.Column="1" Content="{Binding CurrentPlayer.Name}"/>
            <Label Grid.Row="1" Grid.Column="0" Content="Class:"/>
            <Label Grid.Row="1" Grid.Column="1" Content="{Binding CurrentPlayer.CharacterClass}"/>
            <Label Grid.Row="2" Grid.Column="0" Content="Hit points:"/>
            <Label Grid.Row="2" Grid.Column="1" Content="{Binding CurrentPlayer.HitPoints}"/>
            <Label Grid.Row="3" Grid.Column="0" Content="Gold:"/>
            <Label Grid.Row="3" Grid.Column="1" Content="{Binding CurrentPlayer.Gold}"/>
            <Label Grid.Row="4" Grid.Column="0" Content="XP:"/>
            <Label Grid.Row="4" Grid.Column="1" Content="{Binding CurrentPlayer.ExperiencePoints}"/>
            <Label Grid.Row="5" Grid.Column="0" Content="Level:"/>
            <Label Grid.Row="5" Grid.Column="1" Content="{Binding CurrentPlayer.Level}"/>
        </Grid>

        <Grid Grid.Row="1" Grid.Column="1"
              Background="Beige">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="3*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            
            <Border Grid.Row="0" Grid.Column="1"
                    BorderBrush="Gainsboro"
                    BorderThickness="1">
                
                <Grid Margin="3">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    
                    <TextBlock Grid.Row="0"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Name}"/>
                    
                    <Image Grid.Row="1"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Height="125"
                           Width="125"
                           Source="{Binding CurrentLocation.ImageName}"/>
                    
                    <TextBlock Grid.Row="2"
                               HorizontalAlignment="Center"
                               Text="{Binding CurrentLocation.Description}"
                               TextWrapping="Wrap"/>
                </Grid>
                
            </Border>
        </Grid>

        <Grid Grid.Row="2" Grid.Column="0"
              Background="BurlyWood">
        
            <TabControl>
                <TabItem Header="Inventory">
                    <DataGrid ItemsSource="{Binding CurrentPlayer.Inventory}"
                              AutoGenerateColumns="False"
                              HeadersVisibility="Column">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Description"
                                                Binding="{Binding Name}"
                                                Width="*"/>
                            <DataGridTextColumn Header="Price"
                                                Binding="{Binding Price}"
                                                Width="Auto"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </TabItem>
            </TabControl>
        </Grid>

        <Grid Grid.Row="2" Grid.Column="1"
              Background="Lavender">

            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="255" />
            </Grid.ColumnDefinitions>
            
            <Grid Grid.Row="0" Grid.Column="1">

                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Button Grid.Row="0" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveNorth"
                        Visibility="{Binding HasLocationToNorth, Converter={StaticResource BooleanToVisibility}}"
                        Content="North"/>
                <Button Grid.Row="1" Grid.Column="0" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveWest"
                        Visibility="{Binding HasLocationToWest, Converter={StaticResource BooleanToVisibility}}"
                        Content="West"/>
                <Button Grid.Row="1" Grid.Column="2" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveEast"
                        Visibility="{Binding HasLocationToEast, Converter={StaticResource BooleanToVisibility}}"
                        Content="East"/>
                <Button Grid.Row="2" Grid.Column="1" 
                        Height="25" Width="65" Margin="10" 
                        Click="OnClick_MoveSouth"
                        Visibility="{Binding HasLocationToSouth, Converter={StaticResource BooleanToVisibility}}"
                        Content="South"/>

            </Grid>

        </Grid>

    </Grid>
</Window>

 

Step 3: Test, by modifying GameSession.cs

If you want to test the game, and ensure the player’s inventory is displayed, you can modify the GameSession constructor to this code below, with the three lines adding items to CurrentPlayer.Inventory.

 

GameSession.cs

public GameSession()
{
    CurrentPlayer = new Player
                    {
                        Name = "Scott",
                        CharacterClass = "Fighter",
                        HitPoints = 10,
                        Gold = 1000000,
                        ExperiencePoints = 0,
                        Level = 1
                    };

    CurrentWorld = WorldFactory.CreateWorld();

    CurrentLocation = CurrentWorld.LocationAt(0, 0);

    CurrentPlayer.Inventory.Add(ItemFactory.CreateGameItem(1001));
    CurrentPlayer.Inventory.Add(ItemFactory.CreateGameItem(1001));
    CurrentPlayer.Inventory.Add(ItemFactory.CreateGameItem(1002));
}

 

If you add these lines, and run the game, you should see items in the DataGrid for the Player’s Inventory.

After you test that this works, please remove the lines from the GameSesssion constructor (or remember to do that later).

 

Return to main page

11 thoughts on “Lesson 05.2: Creating the Player Inventory

  1. Hi Scott, when trying to run the program I am shown a window saying
    ————–
    Exception Unhandled
    System.InvalidOperationException: ‘Items collection must be empty before using ItemsSource.’
    ————————–
    And told that im in “break mode”.
    This is on Visual Studio 2017, would you happen to know how I might go about solving this issue?

      1. Hi Scott. First of all, thank you very much for your patience and the tutorial.
        I’m using VS2019 for this project and getting the same error as Mr.Joshua which says
        “An unhandled exception of type ‘System.InvalidOperationException’ occurred in PresentationFramework.dll
        Items collection must be empty before using ItemsSource.”
        Is there a solution for this?
        Thanks in advance,
        Sandeep.

  2. Hey Scott,

    I am trying to use the binding method to show some of the attributes assigned to the Weapon game item such as min and max damage, but I am running into a problem with the only things I can display being on the base item, namely, Name and Price. Is there any chance you have some way to help us understand binding a little better?

    1. Hi James,

      This is what I’d do to display the weapon details.

      First, because the Weapon class is a specific child type of GameItem, with properties that don’t exist in the other GameItem child classes (and won’t have properties for MinimumDamage and MaximumDamage), I’d create a new tab for Weapons.

      Next, I’d create a new ObservableCollection property (let’s call it “Weapons”) in the Player class. You’ll use this Weapons property to bind to a datagrid in the new “Weapons” tab. Because the Weapons property’s datatype is Weapon, you’ll have access to all the Weapon properties, including MinimumDamage and MaximumDamage. You’ll need to change the inventory modification functions to add or remove objects from this Weapons property.

      Finally, you might want to not add Weapon objects to the Inventory property – only the new Weapons property.

      Let me know if you try this and have problems or questions.

  3. Hi Scott,

    First I just wanted to thank you for these tutorials.
    Love the way you teach/introduce each concepts.
    It just all flows so well.
    The insights/tips you provide are priceless.
    I’ve picked up soo much.

  4. Hello Scott
    Thank you very much for this tutorial – best I’ve Found anywhere!
    The question I have is this: How would you display a list of items in a grid which would include one or 2 very small graphics? Can you use a List method and bind to it, or do you need another way?

Leave a Reply

Your email address will not be published. Required fields are marked *