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

28 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.

  8. Gary Woodward
    Gary Woodward 2024-04-02

    Scott, great project. I followed the previous one too.
    However, I’m getting an
    [ Invalid expression term ‘.’ ]
    [ Syntax error, ‘:’ expected ]

    protected virtual void OnPropertyChanged(string propertyName)
    {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    For the PropertyChanged?.Invoke(… which has a red squiggly line under it.

    I’ve followed the video through to conclusion but cannot see what has been changed to compensate for this error

    • SOSCSRPG
      SOSCSRPG 2024-04-02

      Hi Gary,

      I think the only place where this expects a “:” is on line 10 of the Player class, as a separator before the INotifyPropertyChanged interface. If that doesn’t help, can you please upload your code for these classes somewhere that I can look at it? https://gist.github.com is a place I like, but any place I can get the files will work.

        • SOSCSRPG
          SOSCSRPG 2024-04-04

          I didn’t see an obvious source of the error in the gist files, so it might help to send me the zip file. Please send it to github@soscsrpg.com

          • Gary Woodward
            Gary Woodward 2024-04-06

            Hi Scott

            I got your email reply. Sent you a followup.

            Having started from scratch, I realised that there was an error in session 3.5 which probably caused the issue. Have it all worked out now though.

            On this session I am getting the error message:
            [ CS1061 “‘MainWindow’…definition for ‘ButtonBase_OnClick’ and no accessible extension method…” Line 50.]

            It runs great and the XP increments perfectly but.. ?

            Is this error ignorable or is it an indicaton of more problems latter?

          • SOSCSRPG
            SOSCSRPG 2024-04-08

            That probably would have happened if you accidentally double-clicked on a button while in the WPF design mode screen in Visual Studio.

            Visual Studio tries to help you and will automatically create an “even handler” if you double-click on something in design mode. The event handler a way to communicate between things. It’s commonly called a “publish/subscribe” pattern. Something in the program “publishes” a message, and everything “subscribed” to the event can do something. In this case it would be the clicking of the button in the UI and the function to run.

            In Visual Studio, in the Solution Explorer, look for MainWindow.xaml.cs (you may need to click the symbol next to MainWindow.xaml to see it). You can go in there, find the ButtonBase_OnClick (it’s probably on line 50) and delete that line.

            Let me know if that doesn’t work. We go into event handlers in a later lesson.

  9. Yago Barbosa
    Yago Barbosa 2024-04-15

    I have a red underline in InitializeComponent();

    CS0103: The name ‘InitializeComponent’ does not exist in the current context

    • SOSCSRPG
      SOSCSRPG 2024-04-16

      Hello Yago,

      Can you please 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?

      If you haven’t used GitHub before, here is a video on how to upload your solution to GitHub and share it with me. https://youtu.be/0si9ElYQv8I

        • SOSCSRPG
          SOSCSRPG 2024-04-17

          Hi Yago,

          The GitHub repository is missing the Engine directory and its files. Can you add those to the repository, or put all the files some place I can download them?

  10. Xander
    Xander 2024-06-08

    enjoying the tutorial so far. out of curiosity why are we not using a constructor for the player class?

    • SOSCSRPG
      SOSCSRPG 2024-06-09

      Thanks! We do use constructors in a future lesson.

Leave a Reply

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