Press "Enter" to skip to content

Lesson 20.3 – Binding list properties to datagridviews

Lesson Objectives

At the end of this lesson, you will know…

  • How to bind list properties of custom classes, to automatically update in datagridviews in the UI

 

Databinding list properties to the UI

In the last lesson we used databinding to connect the integer properties of the Player object to label controls in the UI (CurrentHitPoints, Level, etc.). In this lesson, we’ll bind the list properties for the player’s inventory and quests.

The basic principles are the same as binding an integer property; however, we need to do a few things differently.

 

Step 1: Open the InventoryItem class, in the Engine project.

Just like with the Player class, we need to say that this class implements the INotifyPropertyChanged interface. So, change this:

public class InventoryItem

to this:

public class InventoryItem : INotifyPropertyChanged

While adding this “using” statement, at the top of the file:

using System.ComponentModel;

Then, add the PropertyChanged event and OnPropertyChanged() function.

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

Next, change the Details and Quantity auto-properties to use backing variables and call the OnPropertyChanged () function.

private Item _details;
private int _quantity;
public Item Details
{
    get { return _details; }
    set
    {
        _details = value;
        OnPropertyChanged("Details");
    }
}
public int Quantity
{
     get { return _quantity; }
     set
     {
         _quantity = value;
         OnPropertyChanged("Quantity");
         OnPropertyChanged("Description");
     }
}

 

Step 2: We want the datagridview to display a property of InventoryItem’s Details property. So, we need to do one final thing to the InventoryItem class.

Add this new read-only property, that gets the item’s name from the Item object.

public string Description
{
    get { return Quantity > 1 ? Details.NamePlural : Details.Name; }
}

This way, the InventoryItem object has a Description property we can bind to in the datagrid. We can’t databind to a property of a property, without doing a lot of extra work. So, we’ll use this technique.

 

Step 3: Open the Player.cs class.

To bind a List property, you need to change its datatype to either “BindingList” or “ObservableCollection”. BindingList gives more options than ObservableCollection – like searching and sorting. So, we’ll use it.

Add this to the “using” statements, at the top of the file:

using System.ComponentModel;

Then change the Inventory property from this:

public List<InventoryItem> Inventory { get; set; }

To this:

public BindingList<InventoryItem> Inventory { get; set; }

And change the Player.cs constructor to this, to match the change to the Inventory property’s datatype:

private Player(int currentHitPoints, int maximumHitPoints, int gold, int experiencePoints) : base(currentHitPoints, maximumHitPoints)
{
    Gold = gold;
    ExperiencePoints = experiencePoints;
    Inventory = new BindingList<InventoryItem>();
    Quests = new List<PlayerQuest>();
}

One thing I don’t like in the .NET framework is that the different list/collection datatypes have different ways of working with them. Because we switched from a List datatype, to a BindingList, we need to make a couple of other changes.

In some of the functions of the Player class, we check if an item exists in the player’s inventory – for example, to see if they have all the items needed to complete a quest. We used the .Exists() method on the List properties to do this check. However, .Exists is not available with BindlingLists.

So, in the Player class, use Ctrl-F to search for “Inventory.Exists” and change them to “Inventory.Any”. “Any” will return a “true”, if any of the items in the BindingList match the criteria we are looking for, just like “Exists” does for a List property or variable.

You should find this in two places: the HasRequiredItemToEnterThisLocation() function and the HasAllQuestCompletionItems() function.

 

Step 4: Now that the business objects are ready, we can bind them to the UI.

Open SuperAdventure.cs.

We currently call UpdateInventoryListInUI(), to update the inventory. Just like with the databinding for the integer properties, we’re going to perform the databinding in SuperAdenture’s constructor method, delete the old function, and delete any lines where we call the old function.

In SuperAdventure’s constructor, add these lines after the databinding for the hit points, gold, etc.:

    dgvInventory.RowHeadersVisible = false;
    dgvInventory.AutoGenerateColumns = false;
    dgvInventory.DataSource = _player.Inventory;
    dgvInventory.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Name",
        Width = 197,
        DataPropertyName = "Description"
    });
    dgvInventory.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Quantity",
        DataPropertyName = "Quantity"
    });

These lines configure the dgvInventory datagrid view.

We say that we don’t want to show row headers – the blank squares to the left of each row.

We also don’t want the binding to automatically generate the data grid’s columns. If this value was set to “true”, the datagrid would create a column for each property of InventoryItem. We want to manually configure the columns, so we set AutoGenerateColumns to “false”.

Next, we say that the DataSource for the datagridview is going to be the player’s Inventory property. This is where the data “binds” to the UI.

The next two parts are where we configure the columns of the datagridview.

We add new DataGridViewTextBoxColumns, since we want to display text in them. There are different column types you can use if you want to display buttons – for example.

The DataPropertyName value is the property of InventoryItem that we want to display in the column. This is why we created the Description property in InventoryItem. We couldn’t use “Details.Name” here. We needed a property of InventoryItem for the databinding.

 

Step 5: Now we have the databinding in place, and we can get rid of the old code we used to refresh the Inventory datagridview.

Delete the UpdateInventoryListInUI() function from SuperAdventure.cs. It should be around line 223.

Use Ctrl-F to search for the places in SuperAdventure.cs where we call UpdateInventoryListInUI() and delete those lines. They should be at lines 209, 379, and 463.

 

Step 6: Run the program to make sure everything works.

 

Step 7: We need to repeat these steps to bind the player’s Quests list property to the UI.

Change PlayerQuest.cs to this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Engine
{
    public class PlayerQuest : INotifyPropertyChanged
    {
        private Quest _details;
        private bool _isCompleted;
        public Quest Details
        {
            get { return _details; }
            set
            {
                _details = value;
                OnPropertyChanged("Details");
            }
        }
        public bool IsCompleted
        {
            get { return _isCompleted; }
            set
            {
                _isCompleted = value;
                OnPropertyChanged("IsCompleted");
                OnPropertyChanged("Name");
            }
        }
        public string Name
        {
            get { return Details.Name; }
        }
        public PlayerQuest(Quest details)
        {
            Details = details;
            IsCompleted = false;
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            if(PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
    }
}

 

Step 8: Modify Player.cs

Change the Quests property to a BindingList:

public BindingList<PlayerQuest> Quests { get; set; }

In the Player constructor, change the Quests to be a BindingList also:

Quests = new BindingList<PlayerQuest>();

Find the HasThisQuest() function, and change “Quests.Exists” to “Quests.Any”.

 

Step 9: Modify SuperAdventure.cs

Add this code in the constructor, to configure the datagridview and bind the player’s quest list to the UI.

    dgvQuests.RowHeadersVisible = false;
    dgvQuests.AutoGenerateColumns = false;
    dgvQuests.DataSource = _player.Quests;
    dgvQuests.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Name",
        Width = 197,
        DataPropertyName = "Name"
    });
    dgvQuests.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Done?",
        DataPropertyName = "IsCompleted"
    });

 

Then, delete the UpdateQuestListInUI() function and delete where it was called in the MoveTo() function – around line 227.

 

Step 10: Run your program, and make sure there aren’t any errors.

 

Summary

This may not seem like the most exciting work – making all these changes, without adding any new features to the program. However, refactoring will make it much easier for you to expand the program later.

Plus, now that you know how to do databinding to the UI, you can start out using it the next time – instead of doing it the way we originally did, then making the change to this better method.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next lesson: Lesson 20.4 – Binding child list properties to a combobox

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

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

4 Comments

  1. Jipopotamus
    Jipopotamus January 28, 2017

    This guide is a truly invaluable aid to my c# learnings. I am so greatful for all the time and attention to detail you have put into these thorough tutourials and especially for making it public for free! I owe you a beer!

  2. Hayden
    Hayden October 18, 2020

    Thank you for the guide! I’m not sure if you are still actively checking this as this guide is a couple years old, but since binding the DataGrid to the player’s inventory – it doesn’t delete the rows of the quantity of the item in the inventory hits 0.

    Is there a way I can accomplish this?

    Thank you in advance.

    • Scott Lilly
      Scott Lilly October 19, 2020

      Hi Hayden,

      It should remove the row when the quantity hits 0. 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?

Leave a Reply

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