PropertyChanged is triggered, but the view is not updated - xamarin

I'm changing the label in the class constructor and it works fine, the label is updated ("0"). I'm also trying to update the label when I click in a button, but it's not working ("X"). I noticed debugging that the label value is updated, PropertyChanged is triggered, but the view doesn't change.
public class HomeViewModel : ViewModelBase
{
string playerA;
public string PlayerA
{
get
{
return playerA;
}
set
{
playerA = value;
this.Notify("playerA");
}
}
public ICommand PlayerA_Plus_Command
{
get;
set;
}
public HomeViewModel()
{
this.PlayerA_Plus_Command = new Command(this.PlayerA_Plus);
this.PlayerA = "0";
}
public void PlayerA_Plus()
{
this.PlayerA = "X";
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}

The name of the parameter passed in your PropertyChangedEventArgs is wrong. You are using "playerA" but the name of the (public) property is "PlayerA" (uppercase "P"). Change this.Notify("playerA"); to this.Notify("PlayerA"); or even better:
Notify(nameof(PlayerA));
You can completely get rid of passing the name of the param by adding a [CallerMemberName] attribute to the Notify() method.
protected void Notify([CallerMemberName] string propertyName = null)
This allows you to just call Notify() without parameters and the name of the changed property will automatically be used.

Related

DataBinding issue in Xamarin Forms

I have a new Xamarin Forms 5 app and I'm having trouble with data binding.
First, I display a message that tells the user how many items are in his list. Initially, this is 0. It's displayed by DisplayMessage property of the view model.
Then, the Init() method gets called and once the API call is finished, there are some items in MyList. I put break points to make sure that the API call works and I end up with some data in MyList property.
Because I change the value of message in my Init() method, I was expecting the message to change and display the number of items in the list but it's not changing even though I have some items in MyList.
I created a new ViewModel that looks like this:
public class MyViewModel : BaseViewModel
{
public List<MyItem> MyList { get; set; } = new List<MyItem>();
string message = "You have no items in your list... ";
public string DisplayMessage
{
get => message;
set
{
if(message == value)
return;
message = value;
OnPropertyChanged();
}
}
public async void Init()
{
var data = await _myService.GetData();
if(data.Count > 0)
message = $"You have {data.Count} items in your list!";
MyList = data;
}
}
My MainPage code behind looks like this:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage : ContentPage
{
MyViewModel _vm;
MainPage()
{
InitializeComponent();
_vm = new MyViewModel();
this.BindingContext = _vm;
}
protected override void OnAppearing()
{
base.OnAppearing();
_vm.Init();
}
}
I didn't change anyting in the base view model, except I added my service and it looks like this:
public class BaseViewModel : INotifyPropertyChanged
{
public IMyApiService MyApi => DependencyService.Get<IMyApiService>();
bool isBusy = false;
public bool IsBusy
{
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
string title = string.Empty;
public string Title
{
get { return title; }
set { SetProperty(ref title, value); }
}
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName] string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I'd appreciatae someone telling me where my mistake is. Thanks.
Without seeing the Xaml, I can't 100% answer, but here are a couple of things I see:
You are setting the "message" through the field, not the property. Since you are setting the field directly the OnPropertyChanged event isn't firing so the UI isn't getting notified that the value has changed.
I am guessing you are binding "MyList" to some sort of CollectionView or something? If it's a readonly view, using a List is ok as the collection is never updated. However, if you plan on adding or removing items at runtime, it needs to be an "ObservableCollection" for the same reason as above, the UI isn't notified of new items in a List, but an ObservableCollection will notify the UI of changes to it, so it can update.
Is what Jason mentions above in his comment. The MyList property should be setup like the other properties with the OnPropertyChanged.

Is PropertyChanged += LinkLabel_PropertyChanged; same as protected override void OnPropertyChanged(string propertyName = null)

In a Xamarin template like this. I think there are two ways to check if a property has changed.
Adding PropertyChanged += LinkLabel_PropertyChanged;
Overriding, calling base
If I want to do something when more than one property has changed is there any difference between these two ways of calling a method?
public class LinkLabel : Label
{
public LinkLabel()
{
PropertyChanged += LinkLabel_PropertyChanged;
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
// Check property name and do action here
}
private void LinkLabel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// Check property name and do action here
}
}
For reference here is what I coded and I am wondering if that's a good solution:
public class LinkLabel : Label
{
public LinkLabel()
{
SetDynamicResource(Label.FontFamilyProperty, "Default-Regular");
SetDynamicResource(Label.FontSizeProperty, "LabelTextFontSize");
SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
VerticalOptions = LayoutOptions.CenterAndExpand;
VerticalTextAlignment = TextAlignment.Center;
}
public static readonly BindableProperty IsImportantProperty =
BindableProperty.Create(nameof(IsImportant), typeof(bool), typeof(LinkLabel), false);
public bool IsImportant
{
get { return (bool)GetValue(IsImportantProperty); }
set { SetValue(IsImportantProperty, value); }
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == IsEnabledProperty.PropertyName ||
propertyName == IsImportantProperty.PropertyName)
{
if (this.IsEnabled) {
if (this.IsImportant)
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelImportantColor");
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelColor");
}
else
this.SetDynamicResource(Label.TextColorProperty, "LinkLabelDisabledColor");
}
}
}
Yes, the difference is that registering for the PropertyChanged event works from outside, overriding the protected(!) OnPropertyChanged method works only from within derived classes of Label.
So you would normally only create a new derived LinkLabel class if you want to change the behavior of the label. There, you'd override the OnPropertyChanged (if you need to).
If you want to get informed about a change in your main form, you would register the event directly there. No need to create a derived class.

How can I implement INotifyPropertyChanged to make Xamarin binding update?I

I have this code:
wordGrid.BindingContext = AS.phrase;
AS.phrase = new PSCViewModel() { English = "abcd" };
AS.phrase.English = "JJJJ";
With the setting of BindingContext on the first line I don't see anything in my view. With it after it works and I see "JJJJ".
Here is my viewModel:
public class PSCViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
int id;
string english;
public PSCViewModel()
{
}
public int Id
{
get { return id; }
set
{
if (value != id)
{
id = value;
onPropertyChanged("ID");
}
}
}
public string English
{
get { return english; }
set
{
if (value != english)
{
english = value;
onPropertyChanged("English");
}
}
}
private void onPropertyChanged(string v)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(v));
}
}
}
Can anyone see why the change to the English field would not cause the new value of JJJJ to be displayed?
this is how I implemented INotifyPropertyChanged.
public class Bindable : INotifyPropertyChanged
{
private Dictionary<string, object> _properties = new Dictionary<string, object>();
/// <summary>
/// Gets the value of a property
        /// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns></returns>
protected T Get<T>([CallerMemberName] string name = null)
{
object value = null;
if (_properties.TryGetValue(name, out value))
return value == null ? default(T) : (T)value;
return default(T);
}
/// <summary>
/// Sets the value of a property
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <param name="name"></param>
protected void Set<T>(T value, [CallerMemberName] string name = null)
{
if (Equals(value, Get<T>(name)))
return;
_properties[name] = value;
OnPropertyChanged(name);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here is a sample class describing how to use
public class Transaction : Bindable
{
public Transaction()
{
this.TransactionDate = DateTimeOffset.Now;
this.TransactionType = TransactionType.Add; //enum
this.Quantity = 0;
this.IsDeleted = false;
this.Item = null; //object defined elsewhere
}
public Guid Id { get { return Get<Guid>(); } private set { Set<Guid>(value); } }
public DateTimeOffset? TransactionDate { get { return Get<DateTimeOffset?>(); } set { Set<DateTimeOffset?>(value); } }
public TransactionType TransactionType { get { return Get<TransactionType>(); } set { Set<TransactionType>(value); } }
public double? Quantity { get { return Get<double?>(); } set { Set<double?>(value); } }
public bool? IsDeleted { get { return Get<bool?>(); } set { Set<bool?>(value); } }
public byte[] RowVersion { get { return Get<byte[]>(); } private set { Set<byte[]>(value); } }
public virtual Guid? ItemId { get { return Get<Guid?>(); } set { Set<Guid?>(value); } }
public virtual Item Item { get { return Get<Item>(); } set { Set<Item>(value); } }
}
You probably already found the definition in System.ComponentModel
It's all part of MVVM. Your ViewModel must implement INotifyPropertyChanged. There is only one event in it: PropertyChangedEventHandler PropertyChanged
I usually define a raise method in the ViewModel like this:
protected void RaisePropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then all the properties in the ViewModel must have they getter/setter like this:
public string AProperty
{
get { return aProperty;}
set
{
if(value != aProperty)
{
aProperty = value;
RaisePropertyChanged("AProperty");
}
}
}
Now, when you bind your View with the ViewModel, it will subscribe to PropertyChanged event an propagate the changes. That's it !
A good start would be to read up on the MVVM pattern and how to implement it in Xamarin Forms. Xamarin has their own tutorials on the topic such as this one:
https://developer.xamarin.com/guides/xamarin-forms/xaml/xaml-basics/data_bindings_to_mvvm/
Basically what you do is create a ViewModel which acts as the BindingContext for the entire page. Within that ViewModel you define properties that are bound to your controls such as Labels, ListViews and TextBoxes. In your case the ViewModel would contain a string property called Phrase that is bound to the control called wordGrid.
public class PhraseViewModel
{
public string Phrase {get; set;}
}
Which can be bound in XAML to e.g. a Label like:
<Label Text="{Binding Phrase}" />

WP7 Developpement : How to make the program wait until the end of an EventHandler?

When my view wants the value of LogoStation, it returns null because my program has not yet executed LoadStation_Completed.
I want my program waits that LoadStation_Completed is executed before continuing.
Thx
public class Infos
{
#region propriétés
private DataServiceCollection<SyndicObject> _infosStation;
public DataServiceCollection<SyndicObject> InfosStation
{
get
{
return _infosStation;
}
set
{
_infosStation = value;
}
}
#endregion
string nameStation;
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
private ImageSource _logoStation;
public ImageSource LogoStation
{
get
{
return _logoStation;
}
set
{
_logoStation = value;
NotifyPropertyChanged("LogoStation");
}
}
public Infos(string station)
{
nameStation = station;
getInfos();
}
public void getInfos()
{
SyndicationContext service = new SyndicationContext(new Uri("http://test/817bee9d-faf4-4680-9d05-e41c2c90ae5a/"));
IQueryable<SyndicObject> requete = (from objectSki in service.Objects
where objectSki.NOMSTATION == nameStation
select objectSki);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
InfosStation = new DataServiceCollection<SyndicObject>();
InfosStation.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(InfoStation_LoadCompleted);
InfosStation.LoadAsync(requete);
}
);
}
void InfoStation_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
LogoStation = new BitmapImage(new Uri(#"http://test/upload/" + InfosStation[0].LOGO, UriKind.Absolute));
}
}
By using the property setter you are using NotifyPropertyChanged (correctly) to tell the UI bound to LogoStation that it has been updated. This should mean that the UI will display nothing initially and then the image when the load has completed.
Without seeing your view code what you have here looks correct - apart from the fact that your Infos class doesn't inherit from INotifyPropertyChanged. This means that the event never gets sent.
Update your class definition and you should be good to go.

NavigationService throws NullReferenceException

Using MVVM Light, I'm trying to develop a rather simple WP7 application. I've run into a problem using the navigation service. I can navigate to a page, but after pressing the back button I can't navigate to the same page again. NavigationService throws a NullReferenceException.
I have implemented my navigation using Messaging from the GalaSoft.MvvmLight.Messaging namespace. All my views inherits from a customized PhoneApplicationPage base class that registrers a listener on "NavigationRequest":
public class PhoneApplicationPage : Microsoft.Phone.Controls.PhoneApplicationPage
{
public PhoneApplicationPage() : base()
{
Messenger.Default.Register<Uri>(this, "NavigationRequest", (uri) => NavigationService.Navigate(uri));
}
}
From my view models I post Uri's to this listener:
SendNavigationRequestMessage(new Uri("/View/AppSettingsView.xaml", UriKind.Relative));
Like i said, this works except when navigating after pressing the Back button.
Why is this and how can I solve it?
Is there a better way to implement navigation using MVVM Light?
I'm using MVVM Light as well. I have a class called PageConductor, which is based on what John Papa (Silverlight MVP) from Microsoft uses. Here's the PageConductor Service I use
public class PageConductor : IPageConductor
{
protected Frame RootFrame { get; set; }
public PageConductor()
{
Messenger.Default.Register<Messages.FrameMessage>(this, OnReceiveFrameMessage);
}
public void DisplayError(string origin, Exception e, string details)
{
string description = string.Format("Error occured in {0}. {1} {2}", origin, details, e.Message);
var error = new Model.Error() { Description = description, Title = "Error Occurred" };
Messenger.Default.Send(new Messages.ErrorMessage() { Error = error });
}
public void DisplayError(string origin, Exception e)
{
DisplayError(origin, e, string.Empty);
}
private void OnReceiveFrameMessage(Messages.FrameMessage msg)
{
RootFrame = msg.RootFrame;
}
private void Go(string path, string sender)
{
RootFrame.Navigate(new Uri(path, UriKind.Relative));
}
public void GoBack()
{
RootFrame.GoBack();
}
}
In my MainPage.xaml.cs constructor, I have this, which creates an instance of my ContentFrame in my PageConductor service.:
Messenger.Default.Send(new Messages.FrameMessage() { RootFrame = ContentFrame });
I then use dependency injection to instantiate an instance of my PageConductor Service into my MainPage ViewModel. Here is my MainViewModel class:
protected Services.IPageConductor PageConductor { get; set; }
public RelayCommand<string> NavigateCommand { get; set; }
public MainViewModel(Services.IPageConductor pageConductor)
{
PageConductor = pageConductor;
RegisterCommands();
}
private void RegisterCommands()
{
NavigateCommand = new RelayCommand<string>(
(source) => OnNavigate(source));
}
private void OnNavigate(string sender)
{
PageConductor.GoToView(sender, "main");
}
Notice the instance of my PageConductorService as a parameter in my MainViewModel constructor method. I pass this in via my ViewModelLocator:
private readonly TSMVVM.Services.ServiceProviderBase _sp;
public ViewModelLocator()
{
_sp = Services.ServiceProviderBase.Instance;
CreateMain(_sp);
}
#region MainPageViewModel
public static MainViewModel MainStatic
{
get
{
Services.ServiceProviderBase SP = Services.ServiceProviderBase.Instance;
if (_main == null)
{
CreateMain(SP);
}
return _main;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return MainStatic;
}
}
public static void ClearMain()
{
_main.Cleanup();
_main = null;
}
public static void CreateMain(Services.ServiceProviderBase SP)
{
if (_main == null)
{
_main = new MainViewModel(SP.PageConductor);
}
}
#endregion
For further reference, my Messages.FrameMessage class is simply:
internal class FrameMessage
{
public Frame RootFrame { get; set; }
}
I've had no issues with forward/back buttons.

Resources