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

 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Leave a Reply

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