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:

 

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:

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:

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

Now that the form has this public property, change the btnTrade_Click function, in SuperAdventure.cs, to 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:

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

 

SuperAdventure.cs

Change the btnTrade_Click function to 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”:

 

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:

 

 

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:

 

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:

 

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):

 

We use this code to hide the ItemID columns, by setting their “Visible” status to 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:

 

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 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:

 

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

Get it from GitHub: https://gist.github.com/ScottLilly/7e03a2e73cfb01db5d6d

Or DropBox: https://www.dropbox.com/sh/9eplxj4fvegxvda/AABlcfNR6jqmywcfXY08WvRxa?dl=0

 

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

 

19 thoughts on “Lesson 21.4 – Completing the trading screen

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

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

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

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

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

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

  4. 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! 🙂

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

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

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

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

Leave a Reply

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