1
votes

I recently started learning Xamarin and I stumbled across the following problem. I have a single label in my XAML file which is bound to a ViewModel property. I am using the ICommand interface to bind a tap gesture to a method in my ViewModel which is supposed to update the label's text. However, it is not updating the "Please touch me once!". I am just wondering what I am doing wrong here?

MainPage xaml:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         xmlns:local="clr-namespace:App1"
         x:Class="App1.MainPage">

<Label Text="{Binding MessageContent, Mode=TwoWay}"
       VerticalOptions="Center"
       HorizontalOptions="Center">
    <Label.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding OnLabelTouchedCmd}" />
    </Label.GestureRecognizers>

</Label>

</ContentPage>

Code-behind:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        BindingContext = new MainPageViewModel();
    }
}

ViewModel:

class MainPageViewModel : INotifyPropertyChanged
{
    private string _messageContent;

    public MainPageViewModel()
    {
        MessageContent = "Please touch me once!";

        OnLabelTouchedCmd = new Command(() => { MessageContent = "Hey, stop toutching me!"; });
    }

    public ICommand OnLabelTouchedCmd { get; private set; }

    public string MessageContent
    {
        get => _messageContent;
        set
        {
            _messageContent = value;
            OnPropertyChanged(value);
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;


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

3 Answers

3
votes

You're calling OnPropertyChanged with a wrong argument as seen here:

protected virtual Void OnPropertyChanged ([System.Runtime.CompilerServices.CallerMemberName] String propertyName)

It expects the name of the property instead of the value you're passing now. Try this instead:

public string MessageContent
{
    get => _messageContent;
    set
    {
        _messageContent = value;
         OnPropertyChanged("MessageContent");
    }
}
2
votes

Explaination

The current code isn't working because it is passing the value of the property into OnPropertyChanged.

Instead, we need to pass the name of the property as a string into OnPropertyChanged.

Answer

We can take advantage of the CallerMemberName attribute to make the code more concise and to avoid hard-coding strings when calling OnPropertyChanged.

Adding [CallerMemberName] to the parameter of OnPropertyChanged allows you to call OnPropertyChanged() from the setter of the property, and the property name is automatically passed into the argument.

Updated Method

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

Updated ViewModel

class MainPageViewModel : INotifyPropertyChanged
{
    private string _messageContent;

    ...

    public string MessageContent
    {
        get => _messageContent;
        set
        {
            _messageContent = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

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

Also look at the ViewModelBase located here, have all your ViewModels inherit from it. You can call just OnPropertyChanged, in either of the two ways below. The first of which will just take the name of the calling member, in this case your public property.

OnPropertyChanged();

OnPropertyChanged("MyProperty");

Edit- this is in extension to Brandon's correct answer