Press "Enter" to skip to content

Lesson 20.2 – Binding a custom object’s properties to UI controls

Lesson Objectives

At the end of this lesson, you will know…

  • How to use databinding to automatically update an object’s properties’ values to UI controls (labels)

 

Using databinding to connect values of an object’s properties to the UI

Right now, we have a lot of code in SuperAdventure.cs that reads from the Player object and modifies the labels, buttons, and datagrids in the UI. If we want to add more features to the program, we’ll need to copy that code in more places.

But, duplicating code is a bad programming habit.

If you have a lot of duplicated code, and you ever need to make a change, there’s a good chance you’ll miss one (or more) place. So we’ll use a technique that automatically handles updates, after writing a few lines of code – in one place.

We’ll do this with databinding.

Whenever a property is changed in the player object, the UI will be notified, and the UI will update the appropriate control (labels, for this lesson). Think of this as a “publish/subscribe” technique. The UI will “subscribe” to any changes of the properties, and the Player object will “publish” a notification/event when one of the values changes.

For this lesson, we’ll work with the integer properties: CurrentHitPoints, Gold, ExperiencePoints, and Level.

 

Step 1: Open the SuperAdventure solution in Visual Studio.

 

Step 2: For the UI to know when a value has changed, we need to send a notification from the player object.

Open the LivingCreature.cs class (since it’s the base class for Player, and holds the CurrentHitPoints property).

Change this line:

public class LivingCreature

To this:

public class LivingCreature : INotifyPropertyChanged

This change means that the LivingCreature class needs to implement the INotifyPropertyChanged interface. The INotifyPropertyChanged interface is what the .NET Framework uses for databinding notifications, when a property value changes.

Add this code to the LivingCreature class, to do the notification:

using System.ComponentModel;

and:

public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
    if(PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

The PropertyChanged event is what the UI will “subscribe” to.

The OnPropertyChanged function checks if anything is subscribed to the event. If nothing is subscribed, then PropertyChanged will be null. If PropertyChanged is not null, then another class wants to be notified of changes, so the next line will run, and a PropertyChanged event will “be raised” (the notification will be sent out).

The final step in this class is to “raise the event”, when the CurrentHitPoints value changes. In order to do this, we need to call the OnPropertyChanged() function whenever the CurrentHitPoints value is set.

However, since CurrentHitPoints is an auto-property, we don’t have any place where we can stick in the code to call that function. So, we need to change it to a property with a backing variable.

Change, this:

public int CurrentHitPoints { get; set; }

To this:

private int _currentHitPoints;
public int CurrentHitPoints
{
    get { return _currentHitPoints; }
    set
    {
        _currentHitPoints = value;
        OnPropertyChanged("CurrentHitPoints");
    }
}

Now, the CurrentHitPoints property is a wrapper for the private variable “_currentHitPoints”. When the property is set to a value, the OnPropertyChanged() function will be run, the PropertyChanged event will be raised, and the UI will update the label.

 

Step 3: Now we need to make the changes to the other properties that will use databinding – the ones in the Player class. Open Player.cs and change the Gold and ExperiencePoints auto-properties to this:

private int _gold;
private int _experiencePoints;
public int Gold
{
    get { return _gold; }
    set
    {
        _gold = value;
        OnPropertyChanged("Gold");
    }
}
public int ExperiencePoints
{
    get { return _experiencePoints; }
    private set
    {
        _experiencePoints = value;
        OnPropertyChanged("ExperiencePoints");
        OnPropertyChanged("Level");
    }
}

Notice that the ExperiencePoints property raises a PropertyChanged event for both ExperiencePoints and Level. That’s because we never set the Level value. It’s always calculated from ExperiencePoints.

So, every time the ExperiencePoints property value changes, we’ll also send a notification to update the Level. We could change this to only send a notification when the level changes, but this extra notification won’t hurt us in a small program.

Also, the Player class is using the OnPropertyChanged() function in LivingCreature. That’s because Player inherits from the LivingCreature class. So, it can use any functions (and properties, and events) from LivingCreature – as long as they are scoped (visible) as “public”, “internal”, or “protected”.

 

Step 4: Open SuperAdventure.cs and change the constructor function to this:

 

public SuperAdventure()
{
    InitializeComponent();
    if(File.Exists(PLAYER_DATA_FILE_NAME))
    {
        _player = Player.CreatePlayerFromXmlString(File.ReadAllText(PLAYER_DATA_FILE_NAME));
    }
    else
    {
        _player = Player.CreateDefaultPlayer();
    }
    lblHitPoints.DataBindings.Add("Text", _player, "CurrentHitPoints");
    lblGold.DataBindings.Add("Text", _player, "Gold");
    lblExperience.DataBindings.Add("Text", _player, "ExperiencePoints");
    lblLevel.DataBindings.Add("Text", _player, "Level");
    MoveTo(_player.CurrentLocation);
}

The four new lines bind the labels to the properties.

To break down what’s happening, the first new line means this: For the lblHitPoints control, add a databinding – a “subscription” to a property’s “notifications”. The databinding will connect to the “Text” property of lblHitPoints to the “CurrentHitPoints” property of the “_player” object.

Notice that we added the databindings after we created the object. The object needs to be instantiated before you can bind to it.

We also removed the line that called the UpdatePlayerStats() function. We don’t need to call that method anymore. The databinding will automatically do that for us.

Remove the other lines where we call the UpdatePlayerStats() function. You can find all those places doing a search (Crtl-F).

Also remove any place where we set the “lblHitPoints.Text” property. We don’t need to manually set that value any more.

 

Step 5: Compile your program, run it, and make sure the labels in the UI update when you fight monsters.

 

Summary

Before, the UI would “push” values to the player object’s properties, then it would “pull” values from it, to update the UI.

Now, it still pushes values to the player object, but the player object “pushes” notifications of changes to the UI (and anything else that might be listening). This is one way of “decoupling” – making your program so the classes are more independent.

When your classes are more independent, it’s easier to make changes. You usually need to make fewer changes, in fewer places, when your code is not highly-coupled.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next lesson: Lesson 20.3 – Binding list properties to datagridviews

Previous lesson: Lesson 20.1 – Refactoring the SuperAdventure program

All lessons: Learn C# by Building a Simple RPG Index

48 Comments

  1. Jacob
    Jacob February 4, 2016

    Quick question… I was trying to add the vendor when I had a problem, and you pointed me towards these refactoring parts (which I somehow skipped over). Before refactoring this code I had a button to basically reset the xml file (level/inventory/xp/quests etc) which called the UpdatePlayerStats() function, however after the refactoring it obviously no longer works since that function is gone. My question is what would I need to have that button run to do the new equivalent of that and also reset the DGV’s from the next lesson? Thank you very much again!

  2. Jacob
    Jacob February 4, 2016

    Ok, sorry to post again,but I got the 4 labels to refresh correctly with the following code

    lblHitPoints.Text = _player.CurrentHitPoints.ToString();
    lblLevel.Text = _player.Level.ToString();
    lblExperience.Text = _player.ExperiencePoints.ToString();
    lblGold.Text = _player.Gold.ToString();

    not sure if there is something easier than this, but this works fine for now, as for the DGV I have not figured a way for that yet.

  3. Jacob
    Jacob February 4, 2016

    Ok, I really need to just look closer at the code and read better next time… found out the DGV code

    dgvInventory.DataSource = _player.Inventory;
    dgvQuests.DataSource = _player.Quests;

     

     

    If anyone wants the entire code for the button, create a button on your form named btnReset and double click it to make the btnReset_OnClick function

    private void btnReset_Click(object sender, EventArgs e)
            {
                var confirmResult = MessageBox.Show("Reset your progress?",
                                         "Are you sure?",
                                         MessageBoxButtons.YesNo);
                if (confirmResult == DialogResult.Yes)
                {
                    _player = Player.CreateDefaultPlayer();
                    MoveTo(_player.CurrentLocation);
                    rtbMessages.Clear();
                    lblHitPoints.Text = _player.CurrentHitPoints.ToString();
                    lblLevel.Text = _player.Level.ToString();
                    lblExperience.Text = _player.ExperiencePoints.ToString();
                    lblGold.Text = _player.Gold.ToString();
                    dgvInventory.DataSource = _player.Inventory;
                    dgvQuests.DataSource = _player.Quests;
                }
                else
                {
                    // If 'No', do something here.
                }
            }

     

    • Scott Lilly
      Scott Lilly February 4, 2016

      When you’ve finished the databinding lessons, there is another possible way to reset the player, which might be a little cleaner.

      Step 1: Create a public function in the Player class named “ResetToDefault()”. In this function, set the player’s properties to their default values (the same values as in the CreateDefaultPlayer function).
      Step 2: In SuperAdventure.cs, have the btnReset_Click function call “_player.ResetToDefault();”

      Because you are using the same Player object (_player), which already has its properties bound to the UI, the object will fire its notifications, and the UI should update. That way, you don’t need to manually refresh the UI controls.

  4. Mohammad
    Mohammad April 19, 2016

    Hi, Thanks a lot for your lessons.

    Dropbox link for this lesson is incorrect, please take a look at it.

    Thanks again

    • Scott Lilly
      Scott Lilly April 19, 2016

      You’re welcome. The link should be the correct one now.

      • Mohammad
        Mohammad April 19, 2016

        Thank you, It’s correct.

  5. J
    J May 23, 2016

    I didn’t seem to have System.ComponentModel as a using directive in the livingcreature file, so I added it to make it possible to implement the interface. Should I have had it? I don’t recall skipping any of the lessons…

     

    Makes me wonder why no one else is asking about this?!

    • Scott Lilly
      Scott Lilly May 23, 2016

      Yes. I’ve updated the lesson to explicitly add the using statement. I suspect most people are looking at, or copy-pasting, the code on GitHub – which has the updated using statements.

  6. Jonojojo
    Jonojojo June 7, 2016

    Hi, great tutorial for us beginner to learn C# 🙂

    By the way, when I typed this code:

    if(PropertyChanged != null)
    {
    PropertyChanged(this, new PropertyChangedEventArgs(name));
    }

    Right next to the code, there is a bulb icon appeared saying “Delegate invocation can be simplified” and show potential fix like this:

    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    I think this is a better way to write it? Since it is much simpler, and Visual Studio (2015) recommended it too.

    Thank you for your tutorial I am glad that I found it.

    • Scott Lilly
      Scott Lilly June 7, 2016

      Thank you!

      That technique is called a “null conditional”, and is available in C# 6, which is in the .NET Framework version 4.6. If you’re using that version of the framework, I suggest making the recommended change. It’s cleaner, and it’s almost always a good idea to make your code cleaner. 🙂

  7. blackholeearth0
    blackholeearth0 December 20, 2016

    save load doesnt update    hitpoints exp gold  if i use databinding .

    • blackholeearth0
      blackholeearth0 December 20, 2016

      i was doing save and load by save load button click
      i cleared databindindings and added. now it updates after save load

      lblHitPoints.DataBindings.Clear();
      lblGold.DataBindings.Clear();
      lblExperience.DataBindings.Clear();
      lblLevel.DataBindings.Clear();

      lblHitPoints.DataBindings.Add(“Text”, _player, “CurrentHitPoints”);
      lblGold.DataBindings.Add(“Text”, _player, “Gold”);
      lblExperience.DataBindings.Add(“Text”, _player, “ExperiencePoints”);
      lblLevel.DataBindings.Add(“Text”, _player, “Level”);

      • Scott Lilly
        Scott Lilly December 20, 2016

        Is this with new “save” and “load” buttons you created? If so, that makes sense. The Load would create a new Player object, which would be stored in the _player variable. But, because it’s a new object, you need to set the bindings to it.

  8. A Name
    A Name July 6, 2017

    Just a question. I don’t know if you know that but I can use the same way into my unity project? If you don’t know, do you know someone who can help me?

  9. Christffer Larsson
    Christffer Larsson October 14, 2017

    I have tried multiple times now but I cant get it to work

    using System.ComponentModel;

    but neither
    public class LivingCreature : INotifyPropertyChanged
    nor any other works =/

    I have tried copy the while LivingCreature.cs too but no…
    The type or namespace name ‘INotify.. could not be found
    The type or namespace name ‘PropertyChangedEventHandler.. etc

    • Scott Lilly
      Scott Lilly October 14, 2017

      Can you upload your solution (including the folders under it, and the files in those folders) to GitHub or Dropbox, so I can try to find the source of the problem?

  10. bensh
    bensh January 1, 2018

    Hi Scott,
    thanks again for making this tutorial!
    I have a question on this Part:

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
    if(PropertyChanged != null)
    {
    PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
    }

    even though I know what it does, because of your explanation, and because I see how it works after running the Program, I still don’t know how the UI actually “subscribes” to the event PropertyChangedEventHandler PropertyChanged;

    the event is declared inside the LivingCreature class, so it’s initialized if an Object of the Player class is created. But we never used this PropertyChanged outside of the LivingCreature class so how did the UI actually “subscribe” to that event, and how does the event know, which Property actually changed?

    best regards bensh

    • Scott Lilly
      Scott Lilly January 1, 2018

      Hello Bensh,

      Subscribing to the eventhandler happens in SuperAdventure.Designer.cs. There is more information on how that happens in Lesson 21.3. You can also subscribe to eventhandlers in the form’s class (SuperAdventure.cs). But, when we create the eventhandler through Visual Studio, by double-clicking on a control in the Form Designer, it is added to the Designer.cs file.

    • NOTaROBOT
      NOTaROBOT February 13, 2018

      Also i was thinking about another idea, i wanted make a video on the background ( so background in the menu would move) i already downloaded the video and managed to put that in WindowsMediaPlayer and hided the options in that WindowsMEdiaPlayeR(so only video is leaved on fullscreen) but i faced a problem :
      How to put my buttons above the video so the user can do his things in the menu while the video is playing on background. All my buttons are behind the Video.

      • NOTaROBOT
        NOTaROBOT February 13, 2018

        If it’s difficulte to manage that’s okey i just go to a gif plan

        • Scott Lilly
          Scott Lilly February 13, 2018

          I have not worked with adding videos to a Windows Form program. I’ll see if I can find some ideas, but it might be easiest to use a GIF instead.

    • Scott Lilly
      Scott Lilly February 13, 2018

      That function is handling the Click event on the progressbar – which is why it only runs when the player clicks on the progressbar. What you need is for the program to update the progressbar value whenever the player’s CurrentHitPoints changes.

      I think you can do that by creating databindings for the pBarHealth control, so it looks for changes to the _player.CurrentHitPoints and _player.MaximumHitPoints properties. This would be similar to:
      lblHitPoints.DataBindings.Add("Text", _player, "CurrentHitPoints");

      Try doing that, and let me know if you have any problems figuring out how to do that.

      • NOTaROBOT
        NOTaROBOT February 13, 2018

        I managed to do everthing with timer ( timer just working all the time and sets currentHitPoints into the value ) But i faced another obstacle :

        i didn’t want a timer to always update the numbers of the biggest and smallest numbers in the progressBar so i add it in the initialisation of the “SuperAdventure” like this :
        https://gist.github.com/Ironclad0/3df9387c64769507ef8b13f76ca25dd4, but there is an error System.NullReferenceException ( _player was null )

        • NOTaROBOT
          NOTaROBOT February 13, 2018

          Sorry for disturbing. I found an answer i just needed to put that right after creating _player from xml.

          • Scott Lilly
            Scott Lilly February 14, 2018

            Cool! Finding the source of a problem, and figuring out how to fix it, is an important skill in programming.

  11. Khaled Y Mohammad
    Khaled Y Mohammad July 18, 2018

    I get an error when I make the set for experiencePoints private, deleting the private access modifier fixes it

    • Scott Lilly
      Scott Lilly July 18, 2018

      If that’s happening, then one of your other classes is probably trying to set the ExperiencePoints property’s value. I think you can right-click on “ExperiencePoints” and select “Find references” or “Find usages” to see what other classes use the property (although, that might be a feature of ReSharper, which I have on my computer).

  12. Zodiark
    Zodiark August 10, 2018

    Hello again,
    I’m sorry for filling your website with questions.
    I have a problem in this lesson, I had previously created a Save button and a Load button which contain the “WriteAllText” and “CreatePlayerFromXmlString” commands and method, respectively. Now when I clicked load button, it got me back to the previous save file and I was using UpdatePlayerStats();
    Now, with databindings it does not update the stats anymore when I load the previous save file.

    This is the code: https://pastebin.com/FtA8Ya0W
    The buttons of save and load are at the bottom.

    One last thing, I tried and did not manage to “link” two buttons. I mean, when pressing Load button I made two other buttons visible “btnDecideYesLoad” and “btnDecideNoLoad”. Now I wanted to make them “interactive” with Load button, i.e. Click Load-> Yes and No buttons appear->If “Click Yes” Do a part of code in btnLoad_Click, else “Click No” Do another part of code in btnLoad_Click. Is there any way to do something like this?
    The result I want is:
    Clicking Yes: loads the previous “savefile” (using CreatePlayerFromXmlString)
    Clicking No: makes a new game (using CreateDefaultPlayer)

    Thank you in advance!

    • Scott Lilly
      Scott Lilly August 10, 2018

      Look at the SuperAdventure constructor function. Notice it creates a new Player object, adds databindings, and calls the SetCurrentPlayer function (which stores the Player object in the “_player” variable and calls the UpdateInUI functions). The btnLoad_Click function creates a new Player object, and sets it to the “_player” variable, but it doesn’t redo the databindings or call the functions to update the UI.

      Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it? I’d like to show you some ideas that can handle the respawn and the load (although, I might not be able to work on that until Saturday or Sunday).

        • Scott Lilly
          Scott Lilly August 12, 2018

          I have changes to SuperAdventure.cs here: https://gist.github.com/ScottLilly/89c18629b364611625c783ec933a4ae3

          I moved the databinding code into SetCurrentPlayer. This way, the binding can be reset from any function that calls “SetCurrentPlayer” (the game start, the load button, etc.)

          There is a new function “GivePlayerDefaultItems”. It gives the player the rusty sword and healing potion, if they do not have one in their inventory. It is called when a new player is created, or after removing all the items from the player’s inventory that are not required to enter a location (when they are killed).

          I changed the ResetByDeath function to make changes to the existing _player object, instead of creating a new Player object and cloning it. I added comments to explain what the code is doing.

          I changed btnLoad_OnClick to use the new GivePlayerDefaultItems (when the load fails) and removed the databinding code (because it is now in the SetCurrentPlayer function).

          It looked like everything worked, but I did not do a huge amount of testing. Let me know if you see any problems, or have any questions.

  13. Zodiark
    Zodiark August 14, 2018

    Hello Scott,

    It’s perfect now.
    I went on with your lessons so I have encountered a new problem. It is something very useless for the game itself but I want to get more used to dataSources, somehow.
    I’ll send SuperAdventure.cs updated with your corrections (little things are different):
    can you check from line 344 to 355? They are a comment because I already did that with dgvQuests.DataSource = player Quest in the SetCurrentPlayer(player) method (line 75). But now I can’t type “Yes” and “No” (as I did from 344 to 355) but it gets the value of IsCompleted so it is forced to write “True” and “False”. Is there any way to edit the values of DataSource (before UI can display it)?

    Also, is there a way to link two events? I can’t explain it well, I guess, I’ll try.
    The content of a button_Click can influence the content of a button2_Click?
    button_Click does X, and button2 becomes visible; after I click button2 I want that X becomes Y. And if(Y) do something else.
    Hope it was clear, thanks for your assistance!

    https://github.com/Zodiark1020/SuperAdventure

    • Scott Lilly
      Scott Lilly August 14, 2018

      One way I like to handle displaying a property with a different value (like converting true/false to yes/no) is with a derived property. So, in the PlayerQuest class, add this new property:

      public string IsCompletedYesNo => IsCompleted ? "Yes" : "No";

      This is like using a “get”-only property that uses a ternary operator to look at the value of IsCompleted to determine what to return for IsCompletedYesNo. It’s the same logic as:
      public string IsCompletedYesNo
      {
      get
      {
      if(IsCompleted)
      {
      return "Yes";
      }
      else
      {
      return "No";
      }
      }
      }

      If you do this, you will also need to change the IsCompleted “set” to notify the UI of changes to IsCompletedYesNo, when the value of IsCompleted is changed:
      public bool IsCompleted
      {
      get { return _isCompleted; }
      set
      {
      _isCompleted = value;
      OnPropertyChanged("IsCompleted");
      OnPropertyChanged("IsCompletedYesNo"); // New notification
      OnPropertyChanged("Name");
      }
      }

      I don’t completely understand what you want to do in your second question. But, you probably can do what you want. It sounds like you want to change (or use) a variable from two different button click events. As long as the variable is inside the class, but outside the functions, it is “class-level” and can be used by any of the functions in the class.

      If you can describe exactly what you want to do, or if you try to write the code and have problems, let me know.

  14. Zodiark
    Zodiark August 15, 2018

    Thanks again for the great help! Notification with IsCompletedYesNo works fine, I tried to do something similar but it didn’t work:
    public bool IsCompleted
    {
    get { return IsCompleted ? “Yes” : “No” }
    .
    .
    .
    }
    Of course it didn’t work because it gave a string when it needed a bool.

    About the thing I don’t manage to do, check the new Superadventure.cs in reference to lines 23 and 626 to 684. I hope it is clear what I want to achieve, I cannot handle two buttons that influence one another.

    Code:
    https://gist.github.com/Zodiark1020/9e808b3f770114da4a2bbd219be13b9b

    Thank you again for your patience!

    • Scott Lilly
      Scott Lilly August 17, 2018

      I looked at the SuperAdventure.cs code, but am still not sure what you’re trying to do. I would need the SuperAdventure.Designer.cs file to test the program and see what is happening in the UI.

      Can you describe what you’re trying to do? I see you want to hide/display some controls when the player clicks on the load button. But, what is the purpose of mtbLoadDecide and the two new buttons?

      • Zodiark
        Zodiark August 17, 2018

        Yes, well, the “Yes” button (btnLoadDecideYes), once visible and clicked will effectively load an already saved game. The “No” button (btnLoadDecideNo), once visible and clicked will instead restart a new game, as if the game were missing the xlb file. I want the buttons to be reactive, after I clicked “Load”, that’s it.
        Ignore the mtbLoadDecide, it is only an inscription.

        For Designer.cs: https://github.com/Zodiark1020/SuperAdventure/tree/master/SuperAdventure/SuperAdventure

        Thank you again.

        • Scott Lilly
          Scott Lilly August 19, 2018

          I created the changes I think you want at: https://gist.github.com/ScottLilly/635f08d5bc227fd2cc92844258d9bd89. They are from lines 626 to the end.

          I create two new functions to hide and show the additional “Load” controls: DisplayAdditionalLoadButtons() and HideAdditionalLoadButtons(). Then I changed the other functions to call them. I think this might be easier to understand than having multiple functions set the Visible properties. The btnLoadDecideYes_Click function will load the player from the XML file (if it exists) or call the new CreateNewPlayerForNewGame function. You could also have the SuperAdventure constructor call this function, to create the new player object at the start of the program.

          With the new small functions (with descriptive names), I think the class is easier to understand. If this isn’t what you want the program to do, and you aren’t able to change it how you want, please let me know.

    • Scott Lilly
      Scott Lilly August 21, 2018

      Great! Thanks for sharing your code with everyone.

  15. Piggo564
    Piggo564 February 21, 2020

    Hey, before this lesson, I changed the currentHitPoints label to include ” / maxHitPoints” to show the player what their maximum hit points were. After refactoring, I have no idea how to include that within the same label. Any ideas?

    • Scott Lilly
      Scott Lilly February 21, 2020

      You should be able to do something like this (I haven’t tested this code, so let me know if it doesn’t work):

      In the LivingCreature class, add a new property:

      public string CurrentMaxHitPoints
      {
      get { return CurrentHitPoints.ToString() + ” / ” + MaximumHitPoints.ToString(); }
      }

      Then, bind your label to this new CurrentMaxHitPoints property.

      In the CurrentHitPoints property setter, add another property changed notification, so the UI will know CurrentMaxHitPoints changed when the CurrentHitPoints has changed. Underneath “OnPropertyChanged(“CurrentHitPoints”);”, add:

      OnPropertyChanged(“CurrentMaxHitPoints”);

      Finally, you probably want to change MaximumHitPoints to use a backing variable and do property changed notifications, just like you did with CurrentHitPoints. When you do that, remember to include a property changed notification for CurrentMaxHitPoints here. If the player levels up, and their MaximumHitPoints increases, you’ll want to notify the UI to refresh the label that displays CurrentMaxHitPoints.

      Let me know if that wasn’t clear, or if you have any trouble getting that to work.

  16. Jason S.
    Jason S. November 26, 2020

    Hello, Mr. Lilly!
    Thank you a lot for the lessons you’re sharing with us – they’re very helpful, indeed.

    I’d like to point out one thing I noticed while I was studying this particular lesson – the usage of hardcoded string parameters, such as “ExperiencePoints” in:
    OnPropertyChanged(“ExperiencePoints”);

    I’ve discovered 2 main problems with that practice:
    1. It’s easy to make a mistake while manually typing “ExperiencePoints”. In my personal case, I’ve spent quite a while trying to figure out why the program wouldn’t display any of the player’s values in the UI, because I accidentally omitted the word “Points” and the compiler didn’t react on it.
    2. If we want to change the name of a property, we’ll also have to adjust every single corresponding argument by hand.

    The solution I’d like to propose is to use the “nameof” keyword instead. For example:
    OnPropertyChanged(nameof(ExperiencePoints));
    Or:
    lblExperience.DataBindings.Add(nameof(Text), _player, nameof(_player.ExperiencePoints));

    This method solves all of the aforementioned problems, since the compiler now reacts if it can’t find a property with the same name. Furthermore, if we’re to alter the name of the property, we will be able to use the “Rename” command, which will change it everywhere, including the “nameof” arguments. Plus, IntelliSense will now provide us with a handy list of suggestions for them.

    Thank you again.

    • Scott Lilly
      Scott Lilly November 26, 2020

      Hello Jason!

      Thanks for your comment. You are 100% correct that it’s better to use the “nameof” function. I don’t remember if we change the code to use it in a future lesson, but we do use it in the newer WPF version of these lessons.

      Now that Visual Studio has more built-in refactoring abilities (like renaming a property everywhere), it’s very useful. And, like you mentioned, it’s good to catch mis-typed property names.

  17. Tim
    Tim May 14, 2021

    Hi, after adding the changes in this chapter I now get the exception:
    System.ArgumentException: ‘Cannot bind to the property or column CurrentHitPoints on the DataSource.
    Parameter name: dataMember’
    What am I doing wrong?

    • Scott Lilly
      Scott Lilly May 14, 2021

      Hi Tim,

      The two most-likely things are a typo in the property name (mixing upper and lower-case could cause it) or if the CurrentHitPoints is not set to “public”.

      If that isn’t the source, 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?

Leave a Reply

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