I'm trying to implement ViewModel binding for the first time and am finding that a label that is bound to a property in the viewmodel is not updating to reflect the value I expect it's something silly I'm doing, but would appreciate someone taking a look and helping.
ViewModel:
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
}
public MainPageViewModel(List<Player> _players, GameType _selectedGame)
{
this.Players = _players;
this.SelectedGame = _selectedGame;
//Device.BeginInvokeOnMainThread(() =>
//{
// Players = _players;
// SelectedGame = _selectedGame;
//});
}
private List<Player> players;
public List<Player> Players
{
get { return players; }
set
{
players = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Players")); //might need collectionchanged here
}
}
private GameType selectedGame;
public GameType SelectedGame
{
get { return selectedGame; }
set
{
selectedGame = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedGame"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
GameType:
public class GameType : INotifyPropertyChanged
{
private string _name = "";
public string Name
{
get { return _name; }
set { _name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
//will have scoring and rules
}
MainPage control:
<StackLayout x:Name="stackLayoutScoring" Grid.Column="3" Grid.RowSpan="4">
<Label x:Name="labelGameType" Text="{Binding SelectedGame.Name}" HorizontalTextAlignment="Center" />
MainPageViewModel (with some things I've tried):
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
}
public MainPageViewModel(List<Player> _players, GameType _selectedGame)
{
this.Players = _players;
this.SelectedGame = _selectedGame;
//Device.BeginInvokeOnMainThread(() =>
//{
// Players = _players;
// SelectedGame = _selectedGame;
//});
}
private List<Player> players;
public List<Player> Players
{
get { return players; }
set
{
players = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Players"));
}
}
private GameType selectedGame;
public GameType SelectedGame
{
get { return selectedGame; }
set
{
selectedGame = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("SelectedGame"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
GameStartedEventArgs:
public class GameStartedEventArgs : EventArgs, INotifyPropertyChanged
{
public GameStartedEventArgs(Game game)
{
_Game = game;
}
private Game _game;
public Game _Game
{
get { return _game; }
set { _game = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("_Game")); }
}
public event PropertyChangedEventHandler PropertyChanged; //PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
MainPage code:
public void InitGame(Game game)
{
//initialize points and setup first player
stackLayoutScoring.BindingContext = viewModel;
viewModel.Players = game.Players;
viewModel.SelectedGame = game.SelectedGame;
//stackLayoutScoring.BindingContext = viewModel;
}
Now the viewModel receives the correct values in MainPage.cs, but the labelGameType.Text never updates. I'm not sure if InitGame() getting called from an async method is causing issues:
async void GameStartedAlert(object sender, GameStartedEventArgs e)
{
await DisplayAlert("Success!", Statics.CurrentGame.SelectedGame.Name + ": Game Started!", "OK");
InitGame(e._Game);
}
I'm struggling to spot the issue. I hope it's something daft.
Thanks,
Mike
My problem arose from navigating between pages incorrectly.
I was using
async void NavigateToMainPage(object sender, EventArgs e)
{
await Navigation.PushAsync(new MainPage());
}
which creates a new instance of the MainPage, in parallel to the instance that has the databindings. I fixed it by using:
public void NavigateToMainPage()
{
App.Current.MainPage.Navigation.PopAsync();
}
Thanks to Jason for spotting another issue with my code.