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

Get it from GitHub: https://gist.github.com/ScottLilly/825d78c31698f93a7b86

Or DropBox: https://www.dropbox.com/sh/cdj7mb6hd3tergn/AABif7B9tjJ4kUw5tOzg0zjha?dl=0

 

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

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

Leave a Reply

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