Press "Enter" to skip to content

Lesson 21.4 – Completing the trading screen

Lesson Objectives

At the end of this lesson, you will know…

  • How to open a new form with a button click on a different form
  • How to pass variables to a different form
  • The difference between passing a variable by value, and by reference

 

There are several steps needed, to finish adding the trading screen. When you finish each step, run the program, to be sure the change works. If there is a problem, it will be much easier to find out where it is if you have only made one change.

 

Step 1: In the SuperAdventure project, create a new Windows form named TradingScreen.cs. Do this by right-clicking on the SuperAdventure (UI) project, and selecting Add -> Windows Form.

 

In Visual Studio’s Solution Explorer, select the TradingScreen file, to edit in design (graphic) mode.

Set the form’s properties to these values, using the “Properties” section in the lower-right corner of Visual Studio:

Property Value
Text Trade
Size (Width) 544
Size (Height) 349

 

Next, add these controls to the screen.

 

Labels

Name Text Location (X) Location (Y)
lblMyInventoryMy Inventory 99 13
lblVendorInventoryVendor's Inventory 349 13

 

DataGridViews

Name Location (X) Location (Y) Size (Width) Size (Height)
dgvMyItems 13 43 240 216
dgvVendorItems 276 43 240 216

 

Button

Create one button, and set its properties to these values:

Property Value
Text Close
Name btnClose
Location (X) 441
Location (Y) 274
Size (Width) 75
Size (Height) 23

 

Then, create an eventhandler for the button’s Click event. You can do this by:

  1. Double-click on the btnClose button, in the Design (UI) editor.
  2. Or, select the button in the Design editor, click on the lightning bolt symbol (in the properties area of Visual Studio), and type in the value “btnClose_Click” for the Click event.

Both of these methods will create the eventhandler in TradingScreen.Designer.cs, and the empty function in TradingScreen.cs.

 

Edit TradingScreen.cs, so the btnClose_Click function looks like this:

private void btnClose_Click(object sender, EventArgs e)
{
   Close();
}

 

Now, when the user clicks the “Close” button, the TradingScreen form will close (not be visible anymore).

 

NOTE: If you copy the code from GitHub or Dropbox, and your UI project is not named SuperAdventure, look at the namespace in your TradingScreen.cs file before you paste the code into them. Then, change the namespace from “SuperAdventure” to your namespace, after you paste in the code.

 

Step 2: Now, let’s open up the trading screen when the user clicks on the “Trade” button.

Edit SuperAdventure.cs, and change the btnTrade_Click function to this:

private void btnTrade_Click(object sender, EventArgs e)
{
   TradingScreen tradingScreen = new TradingScreen();
   tradingScreen.StartPosition = FormStartPosition.CenterParent;
   tradingScreen.ShowDialog(this);
}

When the player clicks the “Trade” button, this btnTrade_Click function will run.

It will create a new object/instance of the TradingScreen form, and set its position to the center of its parent. The SuperAdventure form is its parent, because that is where we created the object.

The ShowDialog() function is what makes the TradingScreen form display itself.

 

Build and run the game, to make sure these changes work.

You should be able to click on the “Trade” button, see the new trading screen appear, and click the “Close” button to remove it.

 

Step 3: When we display this form, we want to populate the datagridviews with the inventories – ours and the vendor’s. So, we need to get that information to the form. I’ll show you two ways to do that.

Method 1 – Set a property on the form

The first way is to create a public property on the new form.

Edit TradingScreen.cs, and add this property:

public Player CurrentPlayer { get; set; }

You also need to add this “using” statement, at the top of TradingScreen.cs, for the form to know where the Player class is:

using Engine;

Now that the form has this public property, change the btnTrade_Click function, in SuperAdventure.cs, to this:

private void btnTrade_Click(object sender, EventArgs e)
{
   TradingScreen tradingScreen = new TradingScreen();
   tradingScreen.StartPosition = FormStartPosition.CenterParent;
   tradingScreen.CurrentPlayer = _player;
   tradingScreen.ShowDialog(this);
}

After we instantiate/create the form object, we set its CurrentPlayer property to the _player object in SuperAdventure.cs. So, the functions inside TradingScreen.cs will be able to work with our _player object.

NOTE: When we set CurrentPlayer to the _player object, the game does not create a copy of our _player object, and make a second variable inside TradingScreen.cs. It points to the exact same variable/object that is already in SuperAdventure.

So, if we do something to TradingScreen.CurrentPlayer (such as, sell an item and remove it from CurrentPlayer’s inventory), we will see that item is gone when we look at _player’s inventory.

This is called “using/passing a variable by reference”. There is not a second copy of the variable, only a “reference” to the original variable. For more details, and samples, of how this works, you can read this post: C# – Difference between passing variables by reference and by value.

 

Method 2 – Pass the variable in the constructor

The second way to pass the _player object to another form is to make it a parameter in the constructor. Forms are classes, and have constructors, just like any other class. So, you can also use parameters with them.

To pass the _player object as a parameter, you would make these changes:

TradingScreen.cs

Add this line, inside the class, but outside of any functions, to create a private class-level variable:

private Player _currentPlayer;

Change the constructor to accept a Player parameter, and set the private variable to that value:

public TradingScreen(Player player)
{
   _currentPlayer = player;
   InitializeComponent();
}

 

SuperAdventure.cs

Change the btnTrade_Click function to this:

private void btnTrade_Click(object sender, EventArgs e)
{
   TradingScreen tradingScreen = new TradingScreen(_player);
   tradingScreen.StartPosition = FormStartPosition.CenterParent;
   tradingScreen.ShowDialog(this);
}

 

Now, when we create the new TradingScreen object, we pass in the _player object. When passing a variable as a parameter, it’s still used “by reference”. So, any changes we do to _currentPlayer, in TradingScreen.cs, will also be seen/done in _player, in SuperAdventure.cs.

 

I’m going to use the version where we pass the _player object as a parameter in the constructor, and use the _currentPlayer variable in the functions in TradingScreen.cs. If you want to use the property, you will need to change the rest of the code in this lesson, so it uses “CurrentPlayer”, instead of “_currentPlayer”.

 

After you finish this step, build and run the program. Check that the trading screen displays when you click the “Trade” button.

 

Step 4: The next step is to only show the “Trade” button when there is a vendor at the location. If we don’t do this, we will see an error when we try to bind the non-existent vendor’s inventory to the datagridview.

In SuperAdventure.cs’s PlayerOnPropertyChanged function, find the “if” where we make the changes when the PropertyName == “CurrentLocation” – around line 134, if you have been pasting the previous code into your SuperAdventure.cs file.

Add this line inside that “if”:

btnTrade.Visible = (_player.CurrentLocation.VendorWorkingHere != null);

 

Step 5: For the datagridviews on TradingScreen, we need to include the inventory item’s ID and price. To do this, we need to make a change similar to what we did to display its description.

Edit InventoryItem.cs, and add these new properties:

 

public int ItemID
{
   get { return Details.ID; }
}
public int Price
{
   get { return Details.Price; }
}

 

Now, we can bind to these properties on InventoryItem, and they will show the values from the properties of the Details (Item) object.

 

NOTE: When I first wrote Lesson 21.2, I made the Vendor’s Inventory property a datatype of List<InventoryItem>. It needs to be a BindingList<InventoryItem>. If you made your Vendor class when lesson 21.2 first came out, you need to make this change before the next step:

public BindingList<InventoryItem> Inventory { get; set; }
public Vendor(string name)
{
    Name = name;
    Inventory = new BindingList<InventoryItem>();
}

 

Step 6: We’re finally ready to display the inventories in the datagridviews.

This will be similar to the way we used databinding for dgvInventory, in the constructor of SuperAdventure.cs. The big difference is a new column type that displays a button in each row, to buy or sell that item.

Edit TradingScreen.cs, so it has this constructor code, and these two new functions:

public TradingScreen(Player player)
{
    _currentPlayer = player;
    InitializeComponent();
    // Style, to display numeric column values
    DataGridViewCellStyle rightAlignedCellStyle = new DataGridViewCellStyle();
    rightAlignedCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
    // Populate the datagrid for the player's inventory
    dgvMyItems.RowHeadersVisible = false;
    dgvMyItems.AutoGenerateColumns = false;
    // This hidden column holds the item ID, so we know which item to sell
    dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        DataPropertyName = "ItemID",
        Visible = false
    });
    dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Name",
        Width = 100,
        DataPropertyName = "Description"
    });
    dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Qty",
        Width = 30,
        DefaultCellStyle = rightAlignedCellStyle,
        DataPropertyName = "Quantity"
    });
    dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Price",
        Width = 35,
        DefaultCellStyle = rightAlignedCellStyle,
        DataPropertyName = "Price"
    });
    dgvMyItems.Columns.Add(new DataGridViewButtonColumn
    {
        Text = "Sell 1",
        UseColumnTextForButtonValue = true,
        Width = 50,
        DataPropertyName = "ItemID"
    });
    // Bind the player's inventory to the datagridview 
    dgvMyItems.DataSource = _currentPlayer.Inventory;
    // When the user clicks on a row, call this function
    dgvMyItems.CellClick += dgvMyItems_CellClick;

    // Populate the datagrid for the vendor's inventory
    dgvVendorItems.RowHeadersVisible = false;
    dgvVendorItems.AutoGenerateColumns = false;
    // This hidden column holds the item ID, so we know which item to sell
    dgvVendorItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        DataPropertyName = "ItemID",
        Visible = false
    });
    dgvVendorItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Name",
        Width = 100,
        DataPropertyName = "Description"
    });
    dgvVendorItems.Columns.Add(new DataGridViewTextBoxColumn
    {
        HeaderText = "Price",
        Width = 35,
        DefaultCellStyle = rightAlignedCellStyle,
        DataPropertyName = "Price"
    });
    dgvVendorItems.Columns.Add(new DataGridViewButtonColumn
    {
        Text = "Buy 1",
        UseColumnTextForButtonValue = true,
        Width = 50,
        DataPropertyName = "ItemID"
    });
    // Bind the vendor's inventory to the datagridview 
    dgvVendorItems.DataSource = _currentPlayer.CurrentLocation.VendorWorkingHere.Inventory;
    // When the user clicks on a row, call this function
    dgvVendorItems.CellClick += dgvVendorItems_CellClick;
}
private void dgvMyItems_CellClick(object sender, DataGridViewCellEventArgs e)
{
}
private void dgvVendorItems_CellClick(object sender, DataGridViewCellEventArgs e)
{
}

 

Much of the code is the same as when we bound the player’s inventory to the datagridview on the SuperAdventure screen. But there are a few new things.

First, we created a DataGridViewCellStyle object. You can use these objects to define special formatting for a data grid’s columns. This code creates a style to align the text to the right, instead of the default alignment to the left. We will use it for our numeric columns (quantity and price):

// Style, to display numeric column values
DataGridViewCellStyle rightAlignedCellStyle = new DataGridViewCellStyle();
rightAlignedCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;

 

We use this code to hide the ItemID columns, by setting their “Visible” status to false:

// This hidden column holds the item ID, so we know which item to sell
dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
{
    DataPropertyName = "ItemID",
    Visible = false
});

 

We need to include the ItemID column, so we know which item to buy or sell. However, we don’t want to display it – that number won’t mean anything to the player.

 

For the numeric columns that we want to be right-aligned, we set their DefaultCellStyle property to the rightAlignCellStyle object that we created earlier:

dgvMyItems.Columns.Add(new DataGridViewTextBoxColumn
{
    HeaderText = "Qty",
    Width = 30,
    DefaultCellStyle = rightAlignedCellStyle,
    DataPropertyName = "Quantity"
});

 

To connect clicking the buy/sell buttons with the functions to perform the buying and selling, we add these lines:

// When the user clicks on a row, call this function
dgvMyItems.CellClick += dgvMyItems_CellClick;
// When the user clicks on a row, call this function
dgvVendorItems.CellClick += dgvVendorItems_CellClick;

 

When the user clicks on the “Sell 1” button, in their inventory, the program will call the dgvMyItems_CellClick function, and sell one of that item. When they click on the “Buy 1” button, in the vendor’s inventory, the program will call the dgvVendorItems_CellClick function, and try to buy one of those items.

 

We’ve added a lot of code, so run your program, move the player to the Town Square (the only location with a vendor), and click on the “Trade” button. You should see items in the inventory datagridviews. If you click on the “Sell 1” or “Buy 1” buttons, nothing will happen yet – that’s the next step.

 

Step 7: The final step!

Now we will add the logic to buy and sell items. These functions will increase, or decrease, the player’s inventory and gold.

We also need to add two tests: check if the player has enough gold to buy an item, and don’t let the player sell items where the price is the “unsellable item flag price” we created in Lesson 21.1.

Change the two button click functions to this:

private void dgvMyItems_CellClick(object sender, DataGridViewCellEventArgs e)
{
    // The first column of a datagridview has a ColumnIndex = 0
    // This is known as a "zero-based" array/collection/list.
    // You start counting with 0.
    //
    // The 5th column (ColumnIndex = 4) is the column with the button.
    // So, if the player clicked the button column, we will sell an item from that row.
    if(e.ColumnIndex == 4)
    {
        // This gets the ID value of the item, from the hidden 1st column
        // Remember, ColumnIndex = 0, for the first column
        var itemID = dgvMyItems.Rows[e.RowIndex].Cells[0].Value;
        // Get the Item object for the selected item row
        Item itemBeingSold = World.ItemByID(Convert.ToInt32(itemID));
        if(itemBeingSold.Price == World.UNSELLABLE_ITEM_PRICE)
        {
            MessageBox.Show("You cannot sell the " + itemBeingSold.Name);
        }
        else
        {
            // Remove one of these items from the player's inventory
            _currentPlayer.RemoveItemFromInventory(itemBeingSold);
            // Give the player the gold for the item being sold.
            _currentPlayer.Gold += itemBeingSold.Price;
        }
    }
}
private void dgvVendorItems_CellClick(object sender, DataGridViewCellEventArgs e)
{
    // The 4th column (ColumnIndex = 3) has the "Buy 1" button.
    if(e.ColumnIndex == 3)
    {
        // This gets the ID value of the item, from the hidden 1st column
        var itemID = dgvVendorItems.Rows[e.RowIndex].Cells[0].Value;
        // Get the Item object for the selected item row
        Item itemBeingBought = World.ItemByID(Convert.ToInt32(itemID));
        // Check if the player has enough gold to buy the item
        if(_currentPlayer.Gold >= itemBeingBought.Price)
        {
            // Add one of the items to the player's inventory
            _currentPlayer.AddItemToInventory(itemBeingBought);
            // Remove the gold to pay for the item
            _currentPlayer.Gold -= itemBeingBought.Price;
        }
        else
        {
            MessageBox.Show("You do not have enough gold to buy the " + itemBeingBought.Name);
        }
    }
}

 

For both of these functions, the “e” parameter gives us information about what cell (row/column) was clicked. That is an automatic parameter that is created, and passed, when the CellClick event happens.

We will use the column to make sure the user clicked on a button column – and not one of the other columns. The row will let us determine which item they want to buy or sell.

The functions check the price of the item. If the player tries to sell an item with an unsellable price, the game displays an error message. It also displays an error message if the player tries to buy an item, but doesn’t have enough money for it.

If there is not a problem, the functions increase, or decrease, the player’s gold and inventory. Because the player’s inventory and gold raise events when they are updated, you will automatically see these changes on the SuperAdventure form.

 

Step 8: Compile and run your program. Move the player to the town square location, click on the “Trade” button, and try to buy and sell items.

When the player sells items, we do not add them to the vendor’s inventory. We could, but then we would probably want to add the ability to save, and read, the inventories for all vendors, when the player closes and restarts the game.

You could do that, if you want to expand the game. But I’m going to work on some new lessons on how to use SQL Server to save the player’s game data.

 

Summary

Once you know how to pass variables to other forms, and how some variables are passed “by reference”, you can make larger programs, with more screens.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Next lesson: Lesson 22.1 – Installing MS SQL Server on your computer

Previous lesson: Lesson 21.3 – Add a button and create its eventhandler in code, without the UI design screen

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

55 Comments

  1. PezeM
    PezeM January 25, 2016

    Actually i think u need add
    using SuperAdventure; to SuperAdventure.cs, otherwise it wont open the trading screen.

    • Scott Lilly
      Scott Lilly January 25, 2016

      If you used a different name, when you created the Windows Form project (that I normally call SuperAdventure), then you will need to either add using SuperAdventure; to SuperAdventure.cs, or change the namespace in the TradingScreen files from SuperAdventure, to the name you used when you created the Windows Form project.

  2. Jacob
    Jacob February 3, 2016

    Hello, really liking your guides and just want to say thanks! But I think either I completely missed part of this, or you skipped a step or something. On step 4 it says

    “In SuperAdventure.cs’s PlayerOnPropertyChanged function, find the “if” where we make the changes when the PropertyName == “CurrentLocation” – around line 134, if you have been pasting the previous code into your SuperAdventure.cs file.”

    however it does not look like there is any PlayerOnPropertyChanged function yet in the code. Not sure if i’m just missing something or what, but any help would be grateful. Thanks!

    • Scott Lilly
      Scott Lilly February 3, 2016

      Hi Jacob,

      First off, you’re welcome!

      For the missing code, the PlayerOnPropertyChanged function was created in Step 6 of Lesson 20.4 – Binding child list properties to a combobox (and modified a little more in Lesson 20.5), when we were moving the game’s logic from the SuperAdventure.cs form, into the Player.cs class.

      Please let me know if that does not help fix the problem.

  3. Jacob
    Jacob February 5, 2016

    Ok, I have gotten everything up to date and working, however I have a question about the unsellable items. Is there some way to make it so the gui will not even show the unsellable items instead of just putting a -1 next to it? Thanks!

    • Scott Lilly
      Scott Lilly February 5, 2016

      Unfortunately, there isn’t a super simple way to do that. It would be nice if you could apply a filter to the BindingList, but that is not possible with these built-in Windows Forms controls and collections.

      One way to do this is to create a new property on the Player class:
      public BindingList SellableItems
      {
      get { return new BindingList
      (Inventory.Where(x => x.Price != World.UNSELLABLE_ITEM_PRICE).ToList()); }
      }

      Notice that this creates a “new” BindingList. We need to manually update the data source, after making any changes to the inventory, because this is a new object. We are not updating the object that is already the data grid’s DataSource (which would automatically update the UI). Instead, we are replacing it with a completely new object.

      In TradingScreen.cs, use this new property as the datasource for dgvItems:
      // Bind the player's inventory to the datagridview
      dgvMyItems.DataSource = _currentPlayer.SellableItems;

      Then, in dgvMyItems_CellClick and dgvVendorItems_CellClick, add these lines after successfully buying or selling an item:
      // Update the sellable items
      dgvMyItems.DataSource = _currentPlayer.SellableItems;

      That should filter out the unsellable items from the player’s trading inventory list.

  4. Dave
    Dave April 18, 2016

    I have completed up to 21.4 except for 20.5 and I get the error of “An unhandled exception of type ‘System.NullReferenceException’ occurred in MyRpg.exe”

    It highlights dgvVendorItems.DataSource = _currentPlayer.CurrentLocation.VendorWorkingHere.Inventory;

     

    How can I fix this?

    • Scott Lilly
      Scott Lilly April 18, 2016

      Do you get this error when you run the game and click on the “Trade” button at a location that does not have a trader? If so, make sure you added the line from step 4 of this lesson. That will hide the trade button, when the location does not have a vendor at it (which is when the CurrentLocation.VendorWorkingHere would be null).

      If that isn’t the problem, can you upload your solution to GitHub or Dropbox, so I can look at it?

  5. Ryan
    Ryan May 6, 2016

    Hi, just want to say thanks for these tutorials, they are great!

    And I want to add an issues I am seeing. When you open a trading window and go back to the previous window and click on trade again then you have 2 of the same trading windows open. I can click on trade as many times as I want and it will open an extra trade window each time.

    And the other thing is:

     when I’m in the trade window and I change the Qty column to 0 and then click “sell 1” it sells all those items but I only get the gold amount of 1 of those items.
    When I click on the Qty column and highlight the Qty number and press backspace to remove the Qty number and then click on “sell 1”, I get an error.
    When I decrease the Qty by just 1 and then click on “Sell 1” it sells 2 items but I only get the gold amount of 1

    I think when opening the trade window it must be so that the player can’t change focus to the main window unless he/she closes the trade window first. Or just disable the “Trade” button when opening trade window and enabling the button when player closes the trade window.

    And for the column issue , I think make it read only, so the player can’t change the figures in the columns.

    Probably not something of great issue but worth mentioning if you want to have now bugs.

    Again thanks for the great tutorials! 🙂

    • Scott Lilly
      Scott Lilly May 6, 2016

      You’re welcome, Ryan.

      I tested the program for the situations you described. For the first one (possible to open multiple trading screens), make sure you have: tradingScreen.ShowDialog(this);, and not tradingScreen.Show(this);. “ShowDialog” displays the trading screen as a “modal” screen. That means it locks out all other screens, until you close the trading screen. That would prevent the user from clicking the button multiple times. “Show” is modeless, so the user could click buttons on the other form.

      I wasn’t able to change the value in the Qty column. Are you using XAML for the user interface? If possible, can you upload your copy of the TradingScreen.cs file? That might help me figure out what is happening.

      Thanks!

      • Ryan
        Ryan May 9, 2016

        HI.

        Thanks for the reply.

        TradingScreen.ShowDialog() works perfectly! Thanks! The column thing I found that the dataGridView.ReadOnly was false. So that’s why I could edit the values in-between. I changed it to ReadOnly = true. that fixed it. But I went back to the lesson were you added the DataGridView and you did indicate that ReadOnly must be true. I must have missed that. My bad.

  6. Kyle
    Kyle September 30, 2016

    Hi,

    Loving all the help so far and really coming along and loving it!

    Just one thing I’m stuck on; I’m getting an error I can’t seem to fix. The description is “There is no argument given that corresponds to the required formal parameter ‘player’ of ‘TradingScreen.TradingScreen(Player)”

    and if it’s any help “TradingScreen tradingScreen = new TradingScreen();” – the bold part is underlined in red and the only fix it suggests makes it so nothing shows up in the trade box.

     

    Thanks!

    • Kyle
      Kyle September 30, 2016

      UPDATE: I fixed this error by adding in _player into the brackets after the underlined TradingScreen.

      Thanks

  7. Ren
    Ren February 22, 2017

    Hey Scott,
    Thanks again for doing these. It seems I’ve hit some kind of wall here, though. The DGV’s aren’t populating the name column, nor is it removing items out of the player inventory after you sell them. You can still sell the items, but the UI doesn’t update even though the item’s place in the box still shift up. Is there some code I’m missing?

    • Scott Lilly
      Scott Lilly February 22, 2017

      Can you upload your solution (with all the files in the directories underneath it) to GitHub or Dropbox, so I can look at it?

  8. Ren
    Ren February 23, 2017

    LINK REMOVED FOR PRIVACY Is the link to my solution. Thanks in advance.

    • Scott Lilly
      Scott Lilly February 24, 2017

      It looks like some code from Lesson 20.3 is missing. Make sure the changes from steps 1, 2, and 3 are in the code.

      Please tell me if that does not fix the problems.

      • Ren
        Ren February 25, 2017

        Thanks Scott, I added the code and the names have since populated. However, the price has now disappeared, rendering all items non-sellable and causing the catch to throw an exception.

  9. Jackman10k
    Jackman10k March 6, 2017

    Hello, Mr. Lilly.

    I’ve just about completed the vendor tutorial, but I’m having a small bit of trouble. The trade screen shows up at the right location and the menu pops up with the right information, but the buttons to buy and sell can’t even be clicked (they show up, but don’t slightly light up like clickable buttons do). This leads me to think that there’s an issue with getting the buttons’ desired effects linked to the form, but I’ve walked through the instructions more than once and haven’t seen any mistakes I’ve made or steps I’ve missed. If you could help me out, I would greatly appreciate it.

    • Scott Lilly
      Scott Lilly March 6, 2017

      The first thing I can think of is to check your InventoryItem class, and make sure it has this property:

      public int ItemID
      {
      get { return Details.ID; }
      }

      Then, in the TradingScreen, when you create the button columns, make sure the DataPropertyName is set to “ItemID” (with the same upper and lower-case).

      If that does not make the buttons work, can you upload your current solution, so I can examine it?

      • Jackman10k
        Jackman10k March 7, 2017

        I’ve triple-checked that that information is correct, and after looking at the code for a while longer, I am still at a loss. Chances are good that one of the many changes I’ve made elsewhere is the culprit, but I can’t recall any modifications I’ve made to the inventory save giving items descriptions. I’ve had weirder things happen in my Operating Systems class.

        As always, no rush: LINK REMOVED FOR PRIVACY

        • Scott Lilly
          Scott Lilly March 8, 2017

          The problem is that the datagrids on the TradingScreen are set so their Enabled properties are “false”. That makes is so you cannot click on anything in the datagrids. I’m not sure how that would have happened, but you can fix it by:

          1. Open TradingScreen.Designer.cs, to edit.
          2. Go to lines 66 and 83 (where the datagrids’ Enabled properties are being set).
          3. Change the values from “false” to “true”.

          Please tell me if that does not fix the problem.

          • Jackman10k
            Jackman10k March 8, 2017

            That is the strangest thing. I must’ve forgot to enable them during the design phase, but I’m certain that the Enabled property of Data Grid Views are automatically set to “true” in my release of Visual Studio, as I didn’t have to change that property when designing SuperAdventure.cs. Regardless, your instructions cleared up the problem handily and I am immensely grateful. I’ll definitely keep this fix in mind if it ever pops up again. That I didn’t even think to check the Designer file shows my inexperience, I think.

            On a related note: Do you have any advice on how to limit the quantity of certain items vendors sell? For example, if I want the player to be able to purchase a weapon, but I don’t want them to keep filling their inventory with multiple copies of the weapon (that would be pointless and would make navigating weapon selection tiresome), how would I make the vendor only sell one before removing it from his selection? I envision, before populating the vendor’s inventory, checking the player’s inventory for any of the weapons that could be sold and remove it from the list, but that seems rather inefficient and I’m not sure if it would stop the weapon from being bought immediately after it’s purchased and instead only take effect after the vendor is visited again.

            I’ll definitely be thinking on it, but any sage wisdom you can give would be greatly appreciated.

          • Scott Lilly
            Scott Lilly March 9, 2017

            I believe the default Enabled value for all controls is “true”. But, you are the second person recently to have some controls that were set to “false”. I’m wondering is something is happening (an update in Visual Studio?) Changing the Enabled status of a control is not something I expect that you could do accidentally, and I doubt you would have done the steps to do it on purpose. This is puzzling to me.

            To stop the player from purchasing multiple instance of the same weapon, you’re right that it would be complex to compare the player’s inventory to the vendor’s inventory. What if you add a boolean property to the Item class – PlayerCanOnlyHaveOne (or whatever name you like). Then, in TradingScreen.cs, when the player tries to buy an item, check if that property is true. If so, check if the player already has an item in their inventory with a matching ID. If so, display a warning message and do not allow the purchase. This would be similar to the code that checks if the player has enough gold to buy the item.

  10. Jackman10k
    Jackman10k March 10, 2017

    Definitely a lot more simple than what I had in mind. Your idea worked like a charm, and after already thinking through and implementing the concept of populating the player’s side of the trade screen inventory with only items that the player has that the vendor wants to buy (and squashing all the unexpected bugs that came with it), adding it in was a snap. Thank you very much, Mr. Lilly!

  11. Will
    Will March 22, 2017

    Thank you for the tutorials, they are really good.

    I was wondering how you would filter the _currentPlayer.Inventory list to remove the items with price -1 so they are not shown in dgvMyItems?

    Thanks!

    • Scott Lilly
      Scott Lilly March 22, 2017

      You’re welcome.

      One way to only show the sellable items would be to create another List property in the Player class. It would work similar the Weapons and Potions properties (from lesson 20.4). If you do this, remember to raise a PropertyChanged notification for the new property, whenever items are added to (or removed from) the player’s inventory.

      Let me know if you have any questions about how to do that, or if you have any trouble getting it to work.

  12. Will
    Will March 23, 2017

    I have got it to not show the unsellable items but the dgv does not update when sell is clicked. My code is LINK REMOVED FOR PRIVACY

    In there, it includes vendor item quantities and when you sell something the vendor gains one of that item. I was also wondering how I would go about saving this in the playerdata.xml. I am not using the sql database.

    Thanks, Will

    • Scott Lilly
      Scott Lilly March 23, 2017

      Hi Will,

      Because the new Sellables property is a List, and not a BindingList, the TradingScreen will need to manually update the data when it receives a PropertyChanged event notification. Look in the SuperAdventure.cs file, at the PlayerOnPropertyChanged function (and its eventhandler connection in the SuperAdventure constructor). You do the same, to update the TradingScreen datagrid’s DataSource with the changed Sellables value.

      To save the vendor’s inventory, I would follow a similar pattern to how we save and load the player information. The steps would be:

      1. Create a ToXmlString function in the Vendor class. It would only need nodes for InventoryItems.
      2. Add a FormClosing event to the TradingScreen form (like in Lesson 19.4, step 5). That would write the Vendor.ToXmlString() value to a Vendor.xml file.
      3. Play the game and test that the Vendor.xml file is created correctly – you can open up the Vendor.xml in Visual Studio, or a text editor.
      4. Add code to the TradingScreen constructor to read in the Vendor.XML file. For now, save it to a variable, so you can set a breakpoint and verify it is loaded correctly.
      5. Create a new function in the Vendor class to receive the XML, delete the old inventory items, then refill it with the items in the XML.
      3. In the TradingScreen constructor, after you get the XML (and before you bind the inventory to the datagrid), pass the vendor XML into the new function that will populate its inventory from the XML.

      I would do that step-by-step, checking the program after each step. That is a lot to do, and if you have an error, it’s easier to track down if you only added a little code since the last time the program didn’t have an error.

      Let me know if you have more questions about that.

      • Will
        Will March 25, 2017

        Thanks, I have managed to get all of it working!

  13. Will
    Will March 24, 2017

    Thank you!

  14. Berzerkula
    Berzerkula April 1, 2017

    This project, along with the WPF project, has been very helpful with getting me used to using Visual Studio 2015/2017 community and C Sharp. I come from a linux background with mostly C and the kernel. I have developed most of my troubleshooting skills from that environment.

    This project, along with the WPF project, has a great benefit to new users learning the many fundamentals as a base to learn the more advanced ways to complete a task.

    I decided to go on a whim and wanted the Vendor’s Gold to decrease/increase when an item is sold or bought. When the vendor runs out of gold, a message states the vendor has no more gold. Also, I had the vendor’s inventory list remove/add items as the player sells/buys.

    My next goal is what another user wanted to do, except I also want to save the vendor’s data (gold, inventory list) to the sql database. That is my next task.

    From the WPF project, I started using the nameof() function with the updated property strings.

    I’m also thinking of adding some sound effects from audio embedded resources for when a battle takes place with a monster, the player wins, the monster dies, or the player dies. That would be entertaining.

    I have started from scratch more than once with this project, and each time I build it, I think of something else I’d like to do, or a different way to tackle an objective.

    Thank you for your time and effort for these lessons. It is helping me as I don’t get to program everyday, and I try to keep up with what is out there these days. It is hard to find a tutorial which takes the basics and ends with a practical example like this.

    • Scott Lilly
      Scott Lilly April 2, 2017

      That is great to hear!

      This is a simple game, but it gives a good base to try new techniques and add new features. Adding sounds would be very cool. Last week, someone told me they were adding animated GIFs for the monster images.

      I really like your idea of rewriting the program, and trying something new each time. That sounds like “deliberate practice”, which is supposed to be a powerful way for learn.

  15. Mike
    Mike May 8, 2017

    Hi Scott,
    I have followed your tutorial and I am trying to implement a sell all function that gives you gold for every item sold, instead of just 1. I wondered if you could help me. here is the code that sells all the items

    if (e.ColumnIndex == 5)
    {
    // This gets the ID value of the item, from the hidden 1st column
    // Remember, ColumnIndex = 0, for the first column
    var itemID2 = dgvMyItems.Rows[e.RowIndex].Cells[0].Value;
    var sell = dgvMyItems.Rows[e.RowIndex].Cells[2].Value;

    // Get the Item object for the selected item row
    Item itemBeingSold = World.ItemByID(Convert.ToInt32(itemID2));

    if (itemBeingSold.Price == World.UNSELLABLE_ITEM_PRICE)
    {
    MessageBox.Show(“You cannot sell the ” + itemBeingSold.Name);
    }
    else
    {

    // Remove one of these items from the player’s inventory
    _currentPlayer.RemoveAllFromInventory(itemBeingSold);

    // Give the player the gold for the item being sold.
    _currentPlayer.Gold += itemBeingSold.Price;
    }
    }

    thanks,
    Mike

    • Scott Lilly
      Scott Lilly May 8, 2017

      Hi Mike,

      Did you add another button column, to “Sell All”?

      If so, you will probably need an “else if(e.ColumnIndex == 6)” (or, whatever column holds the new button), to handle selling all the items. That section of code would look in the Player’s inventory and find the quantity of items the player has. You would need to multiply the price by the quantity, before you call the RemoveAllFromInventory (because the Quantity will be zero after that function runs).

      If you still have questions, can you upload the source code for your Player and TradingScreen classes to GitHub, or Gist, so I can look at it?

      • Mike
        Mike May 10, 2017

        Thanks Scott, your tip was very helpful, the sell all function now works perfectly. I went into the Player class and changed it there.

    • Jesse
      Jesse June 6, 2018

      private void dgvMyItems_CellClick(object sender, DataGridViewCellEventArgs e)
      {
      // The first column of a datagridview has a ColumnIndex = 0
      // This is known as a “zero-based” array/collection/list.
      // You start counting with 0.
      //
      // The 5th column (ColumnIndex = 4) is the column with the button.
      // So, if the player clicked the button column, we will sell an item from that row.
      if (e.ColumnIndex == 4)
      {

      // This gets the ID value of the item, from the hidden 1st column
      // Remember, ColumnIndex = 0, for the first column
      var itemID = dgvMyItems.Rows[e.RowIndex].Cells[0].Value;

      // Get the Item object for the selected item row
      Item itemBeingSold = (World.ItemByID(Convert.ToInt32(itemID)));

      if (itemBeingSold.Price == World.UNSELLABLE_ITEM_PRICE)
      {
      MessageBox.Show(“You cannot sell the ” + itemBeingSold.Name);
      }
      else
      {
      // Remove one of these items from the player’s inventory
      _currentPlayer.RemoveItemFromInventory(itemBeingSold);

      // Give the player the gold for the item being sold.
      _currentPlayer.Gold += itemBeingSold.Price;
      }

      }
      else if (e.ColumnIndex == 5)
      {

      // This gets the ID value of the item, from the hidden 1st column
      // Remember, ColumnIndex = 0, for the first column
      var itemID = dgvMyItems.Rows[e.RowIndex].Cells[0].Value;
      int count = Convert.ToInt32(dgvMyItems.Rows[e.RowIndex].Cells[2].Value);

      // Get the Item object for the selected item row
      Item itemBeingSoldByPlayer = (World.ItemByID(Convert.ToInt32(itemID)));

      if (itemBeingSoldByPlayer.Price == World.UNSELLABLE_ITEM_PRICE)
      {
      MessageBox.Show(“You cannot sell the ” + itemBeingSoldByPlayer.Name);
      }
      else
      {
      do
      {

      // Remove one of these items from the player’s inventory
      _currentPlayer.RemoveItemFromInventory(itemBeingSoldByPlayer);

      // Give the player the gold for the item being sold.
      _currentPlayer.Gold += itemBeingSoldByPlayer.Price;

      count –;
      }
      while (count >= 1);
      }
      }

  16. NOTaROBOT
    NOTaROBOT February 10, 2018

    Good evening Scott.
    what is the reason why whe did that :

    DataGridViewCellStyle rightAlignedCellStyle = new DataGridViewCellStyle();
    rightAlignedCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;

    As i got it, it just changes the proporty of the place where the column appears, but i opened the program and looked at the appearance and nothing notable…Only that the columns of qnt and price are smaller, is it? it changes the size ?
    Thank you for lesson !

    • Scott Lilly
      Scott Lilly February 10, 2018

      Normally, the value in a datagrid cell is left-aligned. This should align the value to the right. Try taking a screenshot of the game screen, commenting out those lines, running the program, and comparing the position of the value. It should be a little different.

  17. Martin
    Martin March 4, 2018

    Is this a standard procedure when player start barter with vendor or open inventory/map/journal, that each action open/close a new form window ?

    • Scott Lilly
      Scott Lilly March 5, 2018

      I don’t think it is standard – it’s just the way I built this program. It depends on how you want to program to work. You could open new forms, or you could create a program that only uses one form. If you want everything on one form, you could create a form that has a panel in it. Instead of creating different forms, create different “user controls” or “custom controls”. Then, replace the content of the panel with the user/custom control that you want to display.

  18. ARAS
    ARAS July 29, 2018

    When I click the trade button, its just empty. The vendor and player inventories are empty. I checked the whole code in this lesson 3 times but I can not see a problem. Do you have any idea about what mistake I could do here?

    • Scott Lilly
      Scott Lilly July 29, 2018

      Can you upload your complete solution, so I can look at it? I sent you an email after your previous question. The Dropbox location you sent me did not have all the files for the solution. It was missing the “.sln” file and some of the classes.

  19. Kamal
    Kamal March 26, 2019

    I would add a condition in the base ‘if’ statements from dgvMyItems_CellClick and dgvVendorItems_CellClick methods.
    There is a chance that the user missclicks and hits the header empty cell of the last columns, the ones with the sell and buy buttons, which would cause a System.ArgumentOutOfRangeException.
    The way I found to solve this issue was to add the condition “&& e.RowIndex > -1” to the if statements where you check the column index. Since the index -1 refers to the header Row, there is no need to perform any action when you click on them, so just ignore click events on them.

    • Scott Lilly
      Scott Lilly March 26, 2019

      Thanks for mentioning that. That’s one of the problems of testing your own program – you know what you’re supposed to do, so you don’t do some of the unexpected things that could cause an error.

  20. Raj Amin
    Raj Amin August 24, 2020

    Thank you for this amazing tutorial series, it has taught me a lot about c# that I don’t think I could have learned any other way.

    I’m not sure if you’re still offering support but I was halfway through adding a vendor, then realised I needed something from the refactoring lessons (which i had skipped). Once I came back and finished everything off I didn’t have any errors but when compiling and running I get an exception in the quest data grid view that I don’t understand

    System.InvalidOperationException: ‘ColumnCount property cannot be set on a data-bound DataGridView control.’

    Because I went off on my own to try and put my own spin on the game I’m not sure if I’ve missed something or if I’ve done something wrong

    Here is all my code in a .zip file:
    https://github.com/RajAmin99/RPG_Game

    Thank you so much!

    • Scott Lilly
      Scott Lilly August 24, 2020

      You’re welcome, Raj!

      The problem is because the UpdateQuestListInUI() function is called from several places, and is being configured multiple times.

      The simplest way to fix it is to add “dgvQuests.DataSource = null;” at the start of the function. That will clear out the datagridview before you repopulate it.

      Let me know if that doesn’t work, or if you have any other questions.

      • Raj Amin
        Raj Amin August 25, 2020

        Thank you! That seemed to have solved that problem but now the weapons list doesn’t have any data in it so I cant fight monsters that appear!

        I may go back and redo the refactoring lessons to clean up my code a little bit.

        • Scott Lilly
          Scott Lilly August 26, 2020

          You’re welcome! If you plan to continue with this game, it’s probably a good idea to add in the refactoring. If you want to build the same type of same in WPF (a more modern way to do UIs for C#), and using cleaner code, you might want to look at these lessons: https://www.scottlilly.com/build-a-cwpf-rpg/.

Leave a Reply to Dave Cancel reply

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