1

I've searched and read loads of similar threads, as well as watched some videos, but I'm running into issues and I think it boils down to some misunderstanding. I'm using MVVM with UWP development to study for the 70-357 exam.

My goal: Push a button in my XAML page, have it change a property of an object in my ViewModel, then have that value reflected in the databinding in my XAML view.

In my project I have the following:

MainPage XAML

<StackPanel Name="HPStack" Margin="0,0,10,0">
        <Button Name="HPIncrease" Click="{x:Bind CharacterViewModel.HPIncrease_Click}" Content="+" HorizontalAlignment="Center"/>
        <TextBlock Name="HPTB" Text="{x:Bind CharacterViewModel.myCharacter.CurrentHP}" Margin="0,0,5,0" HorizontalAlignment="Center"/> 
    <Button Name="HPDecrease" Click="{x:Bind CharacterViewModel.HPDecrease_Click}" Content="-" HorizontalAlignment="Center"/>
</StackPanel>

MainPage C#

public CharacterDetailsViewModel CharacterViewModel;

public MainPage()
{
    this.InitializeComponent();
    CharacterViewModel = new CharacterDetailsViewModel();
}

Character View Model (much of this code coming from the explanation I saw here

public class CharacterDetailsViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        //App data
        private Character _myCharacter;
        public Character myCharacter
        {
            set
            {
                _myCharacter = value;
                OnPropertyChanged();
            }
            get { return _myCharacter; }
        }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        //Constructors
        public CharacterDetailsViewModel()
        {
            _myCharacter = new Character(12, 12, 12, 12, 12, 12);
        }

        //buttons
        public void HPIncrease_Click(object sender, RoutedEventArgs e)
        {
            //TODO probably fix this
            myCharacter.CurrentHP++;
        }

        public void HPDecrease_Click(object sender, RoutedEventArgs e)
        {
            myCharacter.CurrentHP--;
        }

    }

Character Model (cutting out a lot of the irrelevant properties)

public class Character
{

    public int TotalHP { get; set; } //TODO not public
    public int CurrentHP {get;set;} //TODO check vs total HP and 0

//constructor for creating a new character with provided stats
public Character (int strength, int dexterity, int constitution, int intelligence, int wisdom, int charisma)
    {
        this.TotalHP = 50;
        this.CurrentHP = 45;
    }
}
  • All of my properties seem to find correctly initially
  • When I click the buttons to change the CurrentHP I don't see this reflected on the XAML
  • I know about prism and template 10 existing. I do not want to use them as I am writing this code to understand these things better in preparation for a certification. In the future I will likely leverage them, but I am trying to figure out what I'm doing wrong in this context.

My understanding so far has been that I should only be putting INotfyPropertyChanged on the ViewModel and it can handle alerting that any property inside of a custom object (Character in this case) has changed.

Am I wrong in this? Do I need INotifyPropertyChanged on my Character class to correctly alert XAML to these changes?

Thanks for any input here.

Community
  • 1
  • 1
Sambardo
  • 714
  • 2
  • 9
  • 15
  • Use `Command` please instead of `click` event. – Nikhil Agrawal Mar 09 '17 at 18:17
  • and have `INotifyPropertyChanged` on `Character` class please. – Nikhil Agrawal Mar 09 '17 at 18:18
  • Two things I see: yes you should raise property changed to inform UI, second - you have defined your binding as *OneTime* - change it to *OneWay* if you want to see changes. – Romasz Mar 09 '17 at 18:18
  • Yes you need raise `PropertyChanged` for `CurrentHP`. Or at least you can raise it for `myCharacter` property that view can update it with new value – Fabio Mar 09 '17 at 18:18
  • @NikhilAgrawal Do you have a good resource on the difference between Command and Click I can take a look at? – Sambardo Mar 12 '17 at 19:20

2 Answers2

3

Two things that prevent your UI from being updated:

  • your Character doesn't implement INotifyPropertyChanged, so UI won't be informed about the change. Implement it and call OnPropertyChanged in apropriate setters, otherwise you will have to call OnPropertyChanged on myCharacter, which should refresh the whole Character, but the first method is better,
  • the second important thing is that it also won't work unless you make you binding OneWay - x:Bind is as default OneTime, so you should correct:
<TextBlock Name="HPTB" Text="{x:Bind CharacterViewModel.myCharacter.CurrentHP, Mode="OneWay"}"...
Romasz
  • 29,662
  • 13
  • 79
  • 154
  • 1
    That's simply not true about the binding direction - bindings are TwoWay by default and setting the binding to OneWay, while it might be advisable, has nothing to do with this problem. – caesay Mar 09 '17 at 18:31
  • 1
    @caesay Take a look at [MSDN - Mode property](https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-bind-markup-extension): `The default is "OneTime".` – Romasz Mar 09 '17 at 18:34
  • You're right! I guess I was thinking about regular WPF and not UWP and just said that impulsively, sorry. – caesay Mar 09 '17 at 18:39
  • @caesay No problem, I also catch up myself on many things that got changed. – Romasz Mar 09 '17 at 18:43
  • It sounds like this + @caesay's answer help explain the issue. x:Bind needs to be changed to OneWay and my Character model needs to have InotifyPropertyChanged implemented as well. Marking this as the answer and I'll try to make the changes when I'm home later. Thanks! – Sambardo Mar 09 '17 at 19:42
2

In your view model, you have implemented INotifyPropertyChanged, so if you set Character to a new object, the event gets executed and WPF will update.

The problem is that when a property of Character changes, no notification event is sent and WPF doesn't know to refresh it's value.

You can either implement the interface in Character, fire the INotifyPropertyChanged on the view model, or change Character's public properties to be dependency properties instead.

caesay
  • 16,932
  • 15
  • 95
  • 160
  • I think this is what I was looking for. So many posts and videos talk about only putting INotifyPropertyChanged on the ViewModel though, any idea why that seems to be stressed to heavily? I'll have to look into dependency properties; I don't think I've ever used them. – Sambardo Mar 09 '17 at 18:27
  • I can't say why people stress something that's wrong. In your example, since you're updating the value from the view-model itself, you can certainly fire the notification event on the view model manually (specify `Character` as the thing that changed). Its just a simple fact that if there is no notification event that WPF can't know about it :) – caesay Mar 09 '17 at 18:30
  • I guess I just thought the code in my setter was saying "Hey, something on this myCharacter property changed" and it would cause the data binding to look at all the properties of it again. Does calling the update on the ViewModel only work if the underlying datatype knows how to do INotifyPropertyChange on its own as well? – Sambardo Mar 09 '17 at 18:35
  • @Sambardo: It does say that - but when does the setter get executed? it only gets executed if you *set* `Character` (to a new object). What you're doing is *getting* `Character`, and then changing a property on it which is completely out of the scope of the property on your view model. – caesay Mar 09 '17 at 18:37
  • that helps a lot. My understanding was that if I changed myCharacter.CurrentHP at all, it was somehow going through the setter in the ViewModel, which is sounds like is incorrect. Could I instead move the OnPropertyChanged() into the buttons themselves to alert it that myCharacter has changed, or is that not considered the right way to do things? – Sambardo Mar 09 '17 at 18:40
  • Ok, you could do that - but then what would happen if something else modifies character? Lets say you're building a game and the player takes damage from a collision with another entity. Now, the hp is potentially being changed by something other than your view model and it can't raise a property changed event. – caesay Mar 09 '17 at 18:43
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137693/discussion-between-caesay-and-sambardo). – caesay Mar 09 '17 at 18:43