Lesson 10.6: Clean up property setters and PropertyChanged notifications

A reader noticed the IsCompleted column in the Quest datagrid is not refreshed when the player completes a quest. That’s because the IsCompleted property in the QuestStatus class was not set to raise a PropertyChanged notification.

This lesson will fix that.

We’ll also remove some setters we don’t need on properties (which will prevent us from unintentionally setting those values elsewhere in the program) and take advantage of the CallerMemberName attribute, to clean up the code a bit.



Lesson Steps

Step 1: Modify Engine\BaseNotificationClass.cs

The first thing we’ll do is simplify raising the PropertyChanged event. Currently, we pass the name of the property into the OnPropertyChanged function, using “nameof(MyProperty)”. That ensures the correct property name is used in the notification.

However, we can simplify that when we want to send a notification for the property whose setter is calling OnPropertyChanged.

Add a new “using” statement, to include System.Runtime.CompilerServices in the class. Then, change the parameter on line 10 to the one shown below. With this change, if we don’t pass a property name into the function, it will use the name of the property that called the function.

If you look at the CurrentLocation setter, in the GameSession class, you’ll see that we call OnPropertyChanged several times. We could change the call on line 50, with “nameof(CurrentLocation)”, to use the parameterless version, because we are calling OnPropertyChanged in the CurrentLocation setter. However, we still need to pass in the property names for the “HasLocation…” properties.





Step 2: Modify Engine\Models\QuestStatus.cs

Change line 3, so this class inherits from BaseNotificationClass. Change the IsCompleted property to use a backing variable, and have it call OnProperyChanged on line 14 – without “nameof(IsCompleted)”, since we are in IsCompleted’s setter.

The PlayerQuest property should never change, once the object is instantiated. So, you can remove the “set;” on line 7. This is so the property can only be set in the constructor. We don’t need to make this change, but it will make our code a bit safer. Now, we can’t make a mistake elsewhere in the program and try to set that property to a different value.

It often helps to write our code so we can’t make a mistake.





Step 3: Modify Engine\Models\GameItem.cs

The properties in these objects should only be set when the object is instantiated. So, we can remove “set;” on lines 5 to 8.





Step 4: Modify Engine\Models\ItemQuantity.cs

Remove “set;” from the properties on lines 5 and 6.





Step 5: Modify Engine\Models\LivingEntity.cs

We can change lines 24, 34, 44, 54, and 64, to use the new parameterless version of OnPropertyChanged.

We’ll also remove the “set;” for the Inventory property (line 68) and the GroupedInventory property (line 70).





Step 6: Modify Engine\Models\Location.cs and World.cs

The properties of the Location objects should never change, once they are instantiated. To make Location like our other classes, we need to add a constructor, so we can get the values to use for the properties. Then, we can remove the “set;” for its properties.

We won’t do this for the TraderHere property, since that value is set after the object is instantiated.

We also need to change the World.AddLocation function, to call the new constructor.








Step 7: Modify Engine\Models\Monster.cs

Remove the “set;” and “private set;” from lines 5 to 7 and line 9.




Step 8: Modify Engine\Models\MonsterEncounter.cs

Remove the “set;” from the MonsterID property. We need to leave it for ChanceOfEncountering, because we may need to reset that property’s value in the Location.AddMonster function. However, we do not need to make this property raise a PropertyChanged notification. Its value is always referenced, each time it’s used.





Step 9: Modify Engine\Models\Player.cs

Change lines 21 and 32 to use the parameterless OnPropertyChanged.

Remove the “set;” from line 38.





Step 10: Modify Engine\Models\Quest.cs

Remove “set;” from all the properties from line 7 through 15.





Step 11: Modify Engine\Models\Weapon.cs

Remove “set;” from the properties on lines 5 and 6.





Step 12: Modify \ViewModels\GameSession.cs

Remove “set;” from line 20.

Use the parameterless OnPropertyChanged on lines 50, 84, and 96.





Step 13: Compile the program, and make sure it runs.

There were a lot of changes, mostly to make the code cleaner. This is often when errors are created.


Return to main page

28 thoughts on “Lesson 10.6: Clean up property setters and PropertyChanged notifications

  1. Hey Scott, another great lesson!  I’ve got a couple of questions:

    First,  we remove the setters for the auto-implemented properties in this lesson, but I don’t understand how we are able to assign values to those properties if they don’t have setters. How does the backing field get set without a setter method?  Is this just for constructors?


    Also,  in my output window of Visual Studio, I’ve noticed some strange errors involving the combo box. Everything is working correctly it seems.  Are these errors indicating some problem in the XAML or the C#?   Below are the errors:


    System.Windows.Data Error: 40 : BindingExpression path error: ‘ID’ property not found on ‘object’ ”Weapon’ (HashCode=33489078)’. BindingExpression:Path=ID; DataItem=’Weapon’ (HashCode=33489078); target element is ‘ComboBox’ (Name=”); target property is ‘NoTarget’ (type ‘Object’)


    System.Windows.Data Error: 4 : Cannot find source for binding with reference ‘RelativeSource FindAncestor, AncestorType=’System.Windows.Controls.ItemsControl’, AncestorLevel=’1”. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is ‘ComboBoxItem’ (Name=”); target property is ‘HorizontalContentAlignment’ (type ‘HorizontalAlignment’)

    System.Windows.Data Error: 4 : Cannot find source for binding with reference ‘RelativeSource FindAncestor, AncestorType=’System.Windows.Controls.ItemsControl’, AncestorLevel=’1”. BindingExpression:Path=VerticalContentAlignment; DataItem=null; target element is ‘ComboBoxItem’ (Name=”); target property is ‘VerticalContentAlignment’ (type ‘VerticalAlignment’)

    1. Thanks Gary!

      Yes, removing the “set;” makes it so the hidden backing variable that the compiler auto-creates can only be set by constructors. If we were to write out the code differently, it would look like this. The “readonly” in this version is what makes it so the variable can only be set by a constructor. Removing “set;” from an auto-property does the same thing:
      public class MyClass
      private readonly int _backingVariableForID;

      public ID
      get { return _backingVariableForID; }

      public MyClass(int id)
      _backingVariableForID = id;

      This is similar to creating immutable (unchangeable) objects in many of the functional languages – such as F#.

      For the error messages you see, can you upload your version of the solution to Dropbox or GitHub, so I can check it out?

  2. Thanks Scott,  that makes sense.  I see how it works now.

    Also, I put my solution and all my files up on a GitHub repository, but I’m not sure how to point you to it.  This is the link , I hope that works.  I think it should.


    1. I found the source of the error message. It took a while, because it’s a runtime error (happens while running the game).

      The weapon ComboBox in MainWindow.xaml is trying to use “ID” for the SelectedValuePath. However, the property on Weapon is actually “ItemTypeID”. You can change it to use ItemTypeID, or just ignore it (and remove “SelectedValuePath=”ID””), since we don’t use SelectedValue for anything. We use CurrentWeapon, which is bound to SelectedItem.

  3. Thanks Scott for your lessons, again! I followed your video step by step but I have a weird issue now when I run the program, a red X appears in this line inside the WorldFactory class.

    newWorld.AddLocation(-2, -1, “Farmer’s Field”, “There are rows of corn growing here, with giant rats hiding between them”, “FarmFields.png”);

    newWorld.LocationAt(-2, -1).AddMonster(2, 100); (here is when the X appears)

    And I get this message.

    System.Reflection.TargetInvocationException: ‘Exception has been thrown by the target of an invocation.’
    NullReferenceException: Object reference not set to an instance of an object.

    Could this happen ‘cos we removed the setters from the Location class? I wrote the constructor following the steps in your video, so I am puzzled..

    EDIT: I removed that line and I get the same error in a similar line, but related to Traders.

    newWorld.LocationAt(-1, -1).TraderHere = TraderFactory.GetTraderByName(“Farmer Ted”);

    EDIT 2: Fixed. The issue was with my Location.cs code, I copied yours from this page and now it works. This was my code


  4. Finally, found the issue with my code. XCoordinate = XCoordinate; in the constructor instead of XCoordinate=xCoordinate . I feel silly now.

    That being said, I am so thankful for your lessons. You’re an excellent teacher, Scott.

    best wishes

  5. If you double click an item in either the inventory or quests tabs besides the headers an exception is thrown. The error is:
    “System.InvalidOperationException was unhandled
    Message: An unhandled exception of type ‘System.InvalidOperationException’ occurred in PresentationFramework.dll
    Additional information: A TwoWay or OneWayToSource binding cannot work on the read-only property ‘Name’ of type ‘Engine.Models.Weapon’.”

    If I change the mode of the xaml binding to oneway it fixes the problem. I don’t know if that is the correct way to fix the problem though. The behavior doesn’t present itself in the traderscreen.

  6. Hi Scott, thank you for great lecture. I am learning so much every day.
    I have a question.
    in LivingEntity.cs

    public ObservableCollection Inventory { get; }

    public ObservableCollection GroupedInventory { get; }

    Wouldn’t it be beneficial to leave them as is {get; set;}?

    Won’t there be chance in the future that creature’s inventory may change for some reason?

    1. For the ObservableCollection properties, and other types of collection properties, you probably only want to initialize (set) them once – inside the class constructor.

      When we want to change the items in the collection, we will call the Add() or Remove() functions on the collection property, instead of making a new list and completely resetting the value of the property.

      Is that clear?

  7. Hi Scott.
    In the MainWindow.xaml.cs file I have using Engine.EventArgs;
    I think at some stage I made a mistake and changed this from EventsArg to EventArgs.
    Now the program does not accept Engine.EventArgs and says EventArgs does not exsist in the namespace Engine.
    Under Engine in the Solutions window I have a folder called EventArgs and under the folder I have GameMessageEventArgs.cs

    Should I have a file called EventArgs.cs under the Engine?


    1. Check inside your GameMessageEventArgs.cs class, and make sure the namespace around the class says “Engine.EventArgs”, and not “Engine.EventArg”. The namespace inside the class is the value you’ll need to have in the “using” statement in other classes.

      If that doesn’t work, can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it?

  8. Hello Scott, thank you for this amazing tutorial! I have one question about objects in the inventory. Currently, when I run the program, I am able to click on the item name in the inventory and take the text out/rename the item while playing the game. How would I go about changing it to where the user can’t edit the item’s name in game?

  9. I noticed what appeared to be a typo in line 13 on Monster.cs but in line 19 you also use the incorrect spelling for maxmumDamage parameter so not a defect. You also correctly spelled the property name. Resulting fine: -3 DKP. 😉

    1. I’ll add that to the list of things to fix in the upcoming “bug fix and cleanup” videos. You’d think I’d be a better typist after working with computers for almost forty years. 🙁

  10. Scott,
    I fixed the Error: 40 that Gary was talking about earlier but also getting this error and seems to be after turning in a quest?

    System.Windows.Data Error: 4 : Cannot find source for binding with reference ‘RelativeSource FindAncestor, AncestorType=’System.Windows.Controls.ItemsControl’, AncestorLevel=’1”. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is ‘ComboBoxItem’ (Name=”); target property is ‘HorizontalContentAlignment’ (type ‘HorizontalAlignment’)

    System.Windows.Data Error: 4 : Cannot find source for binding with reference ‘RelativeSource FindAncestor, AncestorType=’System.Windows.Controls.ItemsControl’, AncestorLevel=’1”. BindingExpression:Path=VerticalContentAlignment; DataItem=null; target element is ‘ComboBoxItem’ (Name=”); target property is ‘VerticalContentAlignment’ (type ‘VerticalAlignment’)


    1. Hi James,

      Are you able to run the program, without errors?

      This looks like one of those weird things that WPF reports as an error, even though it isn’t really an error. If you really want to get rid of the warnings, you can check out https://stackoverflow.com/questions/47391020/cannot-find-source-for-binding-with-reference-relativesource-findancestor or https://stackoverflow.com/questions/15070861/comboboxitem-continues-to-throw-binding-error-despite-style.

      Personally, I just ignore them. That’s probably not the best thing to do, since I might end up ignoring a real problem.

      Let me know if the program has a visible problem (like the quest not being updated, or something). If so, I’d want to investigate that.


      1. Scott,
        The program runs and quest updates and quest says completed, but just one of those things I noticed in the output window. I will look at the links you posted and let you know if I was able to find a solution? Is here or Discord a better place for these type of questions?

        1. James,

          Either place is good for questions.

          This might be better for questions about problems with the code in the lessons, so other people with the same problem can see the solution. Discord might be better if you’re modifying the program and asking questions about your specific changes. But, I usually check both places at the same time.


  11. Scott,
    Okay throwing in the towel. I have no idea how to fix other than ignoring it for now.
    For someone who has mild case of OCD it bothers me 🙂

  12. Scott,
    Theory I have is that System.Windows.Data Error: 4 is somehow caused by the weapon ComboBox having a null default item and when completing the quest the weapon selection is updated. The Error goes back to when we introduced the selection of the combat item.

    I tried this and seems to have cleared the Error: 4
    In the MainWindow.xaml file I gave the ComboBox under the combat controls a name.

    Then in the MainWindow.xaml.cs I added to lines to the MainWindow() function

    This is probably in no way how this should be done and I know that the player has the pointy stick for the first weapon at this point.

    Program ran and Error 4 is gone when I finish the quest.


      1. Scott,
        The code I have above breaks how you select weapons works but just modified to show weapon combobox is causing this ERROR when turning in the quest.
        Something I have noticed with out my breaking modifications is if you sell the pointy stick at another vendor before going back to turn in the quest you don’t get the error.

  13. Scott,
    Think this may have solved the ERROR 4 i was seeing.

    In GameSession.cs I added a little helper function.

    Then I place function before leaving the if statement in the CraftItemUsing function,

    at the end of the UseCurrentConsumable function, and in the CurrentLocation setter.

    https://github.com/Jtaim/SOSCSRPG and see the DefaultWeapon branch.

Leave a Reply

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