I'm trying to get my head around data binding in Xamarin.Forms
. I've read lots of the guides and played with some examples and I am now trying to implement some of my own basic binding.
I've got a Strings file in which I've declared an empty variable:
public static class Strings
{
public static string UserDisplayName;
}
On load of my View, it runs an async function to grab data from a Azure SQL DB which then populates the string
Strings.UserDisplayName = user.FirstName;
In my view page I've bound a label to a variable userDisplayNm
<Label Text="{Binding UserDisplayNm}"></Label>
In my ViewModel I have the following to set UserDisplayNm, however it only ever returns "Welcome, ". How do i get it to fire this again after the sync service has completed & the Strings.UserDisplayname value changes? I think I'm missing a link to an PropertyChanged event or something?
namespace Travel_Comp.ViewModels
{
public sealed class MenuViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MenuViewModel()
{
this.UserDisplayNm = Strings.UserDisplayName;
}
public string UserDisplayNm
{
set
{
if (Strings.UserDisplayName != value)
{
value = Strings.UserDisplayName;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("UserDisplayNm"));
}
}
}
get
{
return "Welcome, " + Strings.UserDisplayName;
}
}
}
}
EDIT:
Thanks for your replies. I think I'm getting closer based on the replies below, here is what I've now got, although The MenuViewModel.LoadAsync() is throwing an error "Inaccessible due to its protection level", so i can't compile to check it yet. Is this what you were suggesting & any ideas on the Protection level issue??
Strings file:
public static class Strings
{
public static string UserDisplayName;
}
ViewModel:
namespace Travel_Comp.ViewModels
{
public sealed class MenuViewModel : INotifyPropertyChanged
{
//Azure sync process
ServerManager manager;
public event PropertyChangedEventHandler PropertyChanged;
public MenuViewModel()
{
//Initial set of UserDisplayNm
this.UserDisplayNm = Strings.UserDisplayName;
}
async void LoadAsync()
{
try
{
//Run process to populate Strings.UserDisplayNm, set Syncitems to true to sync with Server
foreach (var user in await manager.GetUsersAsync(syncItems: true))
{
Strings.UserDisplayName = user.FirstName;
}
}
catch (Exception e)
{
Console.WriteLine($"Error while retrieving user name: {e}");
}
}
public string UserDisplayNm
{
set
{
if (Strings.UserDisplayName != value)
{
value = Strings.UserDisplayName;
if (PropertyChanged != null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
}
}
}
get
{
return "Welcome, " + Strings.UserDisplayName;
}
}
}
}
View:
protected override async void OnAppearing()
{
base.OnAppearing();
ViewModels.MenuViewModel.LoadAsync();
}
So if you're looking some guidance for MVVM, you should know that usually you put your dependencies in your view model constructor, here your Azure service.
Also you could use a existing MVVM framework that will make things easy for you, like Prism or FreshMVVM .
But if you want to go for full vanilla you can also call your vm code from the view code behind.
So I'm suggesting this modification to your MenuViewModel
:
private IAzureService _azureService;
private string _userDisplayNm;
public MenuViewModel(IAzureService azureService)
{
_azureService = azureService;
}
public string UserDisplayNm
{
get
{
return _userDisplayNm;
}
set
{
if (_userDisplayNm != value)
{
_userDisplayNm = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(UserDisplayNm)));
}
}
}
public async void LoadAsync()
{
try
{
UserDisplayNm = await _azureService.GetUserName();
}
catch (Exception exception)
{
Debug.WriteLine($"Error while retrieving user name: {exception}")
}
}
Then in you view code behind:
void OnAppearing()
{
_menuViewModel.LoadAsync();
}
To resolve the question: Inaccessible due to its protection level
, you can try to add the public
access modifier before the function of LoadAsync
.
public async void LoadAsync(){
//....
}
And I have created a simple demo to simulate your code.
The main code is:
public sealed class TestModel: INotifyPropertyChanged
{
//*******************************************
string _userDisplayName;
public string UserDisplayName {
set { SetProperty(ref _userDisplayName, value); }
get { return _userDisplayName; }
}
public async void LoadAsync()
{
try
{
UserDisplayName = "updated value: Angela";
Strings.UserDisplayName = UserDisplayName;
}
catch (Exception exception)
{
Debug.WriteLine($"Error while retrieving user name: {exception}");
}
}
bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (Object.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
xaml
<Label Text="{Binding UserDisplayName }" BackgroundColor="Yellow"
VerticalOptions="Center" HorizontalOptions="Fill" HeightRequest="50" />
<Button Text="update the Label value" Clicked="Button_Clicked"/>
And use like this:
public partial class MyPage1 : ContentPage
{
TestModel model;
public MyPage1 ()
{
InitializeComponent ();
model = new TestModel();
BindingContext = model;
}
private void Button_Clicked(object sender, EventArgs e)
{
model.LoadAsync();
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.