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:

To this:

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:

and:

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:

To this:

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:

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:

 

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

36 thoughts on “Lesson 20.2 – Binding a custom object’s properties to UI controls

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

     

    1. 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. Hi, Thanks a lot for your lessons.

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

    Thanks again

  5. 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?!

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

    1. 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. 🙂

    1. 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”);

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

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

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

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

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

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

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

      1. 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 )

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

    1. 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).

  11. 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!

    1. 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).

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

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

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

Leave a Reply

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