Press "Enter" to skip to content

Lesson 03.6: Update Player data with the PropertyChanged event

In this lesson, we will finish connecting the Model (Player.cs) to the View (MainWindow.xaml). With this change, when a property value changes on the Model or ViewModel, the View will automatically update.

Summary

  • An “Interface” defines the properties and functions that must exist in any class that “implements” the interface.
    • It also lets other classes know how the classes with the interface will work, and how they can be used.
  • Databinding does not automatically know when a property value changes in the DataContext object.
    • The View can know about changes to properties, if the ViewModel (or Model) classes implement the INotifyPropertyChanged interface.
    • When a class implements INotifyPropertyChanged, its properties “raise” a PropertyChanged “event”. The View “listens” for that event, and updates the UI, when it receives notification of the change.
  • To make the property raise the PropertyChanged event, when it gets a new value, they cannot be auto-properties.
    • Add the “using System.ComponentModel;” using directive
    • We need to add extra code to the property “set”, to raise the Property Changed event, when the property is set to a new value.
    • To add this extra code, we need to add a “backing variable” for the property – a variable the property uses to store its value.
    • Then, we need to add a code to raise the PropertyChanged event, for anything that may be subscribed to the eventhandler, such as the View.

Source Code

Player.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine.Models
{
    public class Player : INotifyPropertyChanged
    {
        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("Name");
            }
        }
        public string CharacterClass
        {
            get { return _characterClass; }
            set
            {
                _characterClass = value; 
                OnPropertyChanged("CharacterClass");
            }
        }
        public int HitPoints
        {
            get { return _hitPoints; }
            set
            {
                _hitPoints = value; 
                OnPropertyChanged("HitPoints");
            }
        }
        public int ExperiencePoints
        {
            get { return _experiencePoints; }
            set
            {
                _experiencePoints = value; 
                OnPropertyChanged("ExperiencePoints");
            }
        }
        public int Level
        {
            get { return _level; }
            set
            {
                _level = value; 
                OnPropertyChanged("Level");
            }
        }
        public int Gold
        {
            get { return _gold; }
            set
            {
                _gold = value;
                OnPropertyChanged("Gold");
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
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"
        Title="Scott's Awesome Game" Height="768" Width="1024">
    <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}"/>
            <Button Grid.Row="6" Grid.Column="1" Content="Add XP" Click="ButtonBase_OnClick"></Button>
        </Grid>
        <Label Grid.Row="1" Grid.Column="1" Content="Game Data" Background="Beige"/>
        <Label Grid.Row="2" Grid.Column="0" Content="Inventory/Quests" Background="BurlyWood"/>
        <Label Grid.Row="2" Grid.Column="1" Content="Combat/Movement Controls" Background="Lavender"/>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Engine.ViewModels;
namespace WPFUI
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private GameSession _gameSession;
        public MainWindow()
        {
            InitializeComponent();
            _gameSession = new GameSession();
            DataContext = _gameSession;
        }
        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            _gameSession.CurrentPlayer.ExperiencePoints = _gameSession.CurrentPlayer.ExperiencePoints + 10;
        }
    }
}

NEXT LESSON: Lesson 04.1: Creating the Location class

PREVIOUS LESSON: Lesson 03.5: Displaying the Player Object

16 Comments

  1. Tony
    Tony 2022-03-11

    Hey Scott,

    I am getting an error in the MainWindow.xaml of “MC3000 Name cannot begin with the < character, hexadecimal value 0x3C. Line 24, position 13. XML is not valid"

    I tried deleting all of my code and re copying yours verbatim and it still throws this error. It runs but that error won't go away. Any tips would be appreciated!

    • SOSCSRPG
      SOSCSRPG 2022-03-11

      Hi Tony,

      First, please try doing a “Clean Solution” then “Build Solution”. That might eliminate the error.
      Visual Studio Clean Solution Build Solution

      If that doesn’t work, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub, Dropbox, or some other file-sharing location so I can look at it?

  2. Alex Utkin
    Alex Utkin 2022-06-06

    Hello! Thank you for your tutorial. I stumbled on a very strange behavior. I have noticed that when you bind your data in XAML, Intellisense doesnt work. There are three dots under the property name and in the tooltip message box it is said that there is no context for binding. After googling how to solve a problem (many ppl have it as it seems) I found a workaround. you just put this

    Inside the grid where you bind the property names.
    BUT! if you leave these lines there. The update on property change isnt reflected in MainWindow. Maybe it is minor issue but I’ve spent 5 hours trying to understand where i had done wrong. Im still new to delegates and events concepts.

    So if someone doesnt have Intellisense working in XAML and have lots of property name to bind. This workaround helps dont forget to remove or comment out it,

    • Alex Utkin
      Alex Utkin 2022-06-06

      Sorry the code I posted in original message did not go through perhaps due to special symbols. After words “you just put this”


      <Grid.DataContext>
      <eng:GameSession>
      <Grid.DataContext>

      Dont forget the angular brackets

      • SOSCSRPG
        SOSCSRPG 2022-06-06

        Thank you for sharing that Alex. I fixed the code part, since the website has to remove less-than and greater-than signs (it’s a potential way that hackers could inject code into the site).

  3. Alex Utkin
    Alex Utkin 2022-06-07

    Hi again, I did some additional testings on Intellisense and XAML workaround. Though original sources stated that it doesnt work. It did work for me. Instead of putting Grid.DataContext (or rather AnyContainer.DataContext) inside the immediate container. You can put it globally using Window.DataContext. You have to include xmlns key and define namespace for the class you want.

    and I used (angular brackets where needed)

    <Window.DataContext>
    <eng:GameSession>
    <Window.DataContext>

    It may be underlined by compiler and it can keep saying that there is no context for that namespace, but Intellisense will work in Designer Window. To remove the namespace error, you have to fix other possible errors in your solution and build the project. When the project is built successfully the namespace error for DataContext goes away. It was all done on VS 2022.

  4. Alex Utkin
    Alex Utkin 2022-06-07

    Again dont forget to remove or comment out Window.DataContext. Or it can make UI not updating after INotifyPropertyChange implementation in the next chapters.

  5. Javi Flores
    Javi Flores 2022-09-09

    I was confused at first, I was not able to implement “INotifyPropertyChanged” and I realized its because in the video it updated with a new using directive but I did not notice it, all good now. For anyone else who is lost just add it

    using System.ComponentModel;

    • SOSCSRPG
      SOSCSRPG 2022-09-12

      Thank you for mentioning that. I updated the instructions to include adding the “using” directive.

    • Chris
      Chris 2023-08-20

      I had this too and was confused by it – I thought VS22 (which is what i’m running) auto-updated the include files? It did on an earlier lesson anyway.

      BTW thanks Scott, I’m enjoying the lessons, it’s a fun project 🙂

  6. Gus
    Gus 2023-02-04

    I’m not sure I totally understand the purpose of backing variables, they seem sort of redundant when we already have the property declarations. Granted, I’m more used to Java where you would do something like:

    private int property;

    public int getProperty() {
    return property;
    }

    public void setProperty(int value) {
    property = value;
    }

    i.e. A private variable with public getter and setter methods that change the variable itself. Is there a reason we don’t/can’t replicate this in C#? Thanks.

    • SOSCSRPG
      SOSCSRPG 2023-02-05

      We are doing the same thing as the Java example – with one difference. In our setters, we also Call the OnPropertyChanged function, so anything binding to the property (like the UI) is notified the value change and they need to deal with the change.

      There are a couple syntactic differences in C# example. For the setter and getter, we don’t need to have “set” and “get” in front of the property name, and the setter does not require defining the “(int value)”, since the datatype is already declared in “public string Name”. But, it’s the same basic idea for both Java and C# – there’s a way to get and to set a property, and the value is stored in a backing variable.

  7. Lorenzo
    Lorenzo 2023-07-02

    man this lesson went over my head…

    • SOSCSRPG
      SOSCSRPG 2023-07-11

      You might want to look on YouTube for other videos on C# PropertyChanged notifications. It’s a very common, and important, thing to understand, since many programs use it.

Leave a Reply

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