I got two pages, "HomePage", "SettingPage", including the same "MyView" (some Pickers there). When I click "Go Setting"(or show more settings) Button from Homepage, the values syncs to the setting page. But When I click "Apply" on the setting page, the values did not come back.

I am new in c# and Xamarin and tried to search online and Microsoft docs. But I couldn't find a way to fix this issue.

Also I was following this link: How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms? and did the same global value in my code.

  1. MyView (ContentView)
public MyView()
{
    InitializeComponent();
    BindingContext = GlobalVar.MyViewModel;

    Setting1.SetBinding(Picker.ItemsSourceProperty, "ObList1");
    Setting1.ItemDisplayBinding = new Binding("obj_text");
    Setting1.SetBinding(Picker.SelectedItemProperty, "SelectedItem1");
    //also other pickers
}
  1. HomePage (including the MyView)
public SearchPage ()
{
    InitializeComponent ();
    BindingContext = GlobalVar.MyViewModel;
}

private async void Click_GoSetting(object sender, EventArgs e)
{
    await Navigation.PushAsync(new SettingPage());
}
  1. SettingPage (including the same MyView)
public partial class SettingPage : ContentPage
{
  MyViewModel viewModel { get; set; } = GlobalVar.MyViewModel;

  public SettingPage ()
  {
    BindingContext = viewModel;
  }

  private async void Click_ApplySetting(object sender, EventArgs e)
  {
    await Navigation.PopAsync(true);
  }

  //some other method deal with viewModel
}
  1. GLobalVar.cs
        private static  MyViewModel _myViewModel = new MyrViewModel();
        public static MyViewModel MyViewModel
        {
            get
            {
                return _myViewModel;
            }
        }
  1. ViewModel
    public class MyViewModel : BaseViewModel
    {
        public ObservableCollection<obj> ObList1 { get; set; }
        public ObservableCollection<obj> ObList2 { get; set; }
        public ObservableCollection<obj> ObList3 { get; set; }
        public obj SelectedItem1 { get; set; }
        public obj SelectedItem2 { get; set; }
        public obj SelectedItem3 { get; set; }

        public MyViewModel()
        {
            ObList1 = new ObservableCollection<obj>();
            ObList2 = new ObservableCollection<obj>();
            ObList3 = new ObservableCollection<obj>();
        }
    }

Maybe I should notify the changes on my SettingPage to viewmodel? or do something in the "set" in viewmodel?

The confusing point is that two pages embed the same view using the same viewmodel, but notify the change from Page1 to Page2 only, not Page2 to Page1.

Any ideas, thx in advance.

2 Answers

0
Junior Jiang - MSFT On Best Solutions

Solution One:

Using Event can pass value back to Previous Page.

Define Event in SecondPage :

public delegate void EventHandler(string status);
public event EventHandler EventPass;

Invoke Event when Page disappear:

protected override void OnDisappearing()
{
    base.OnDisappearing();
    EventPass("Back Code");
}

In FirstPage, when Naviagtion place need to add the Event here:

string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);

Now value will be passed here:

private void PageSecond_EventPass(string status)
{
    Title = status;
    Console.WriteLine("---" + status);
}

Solution Two:

Using Properties Dictionary to store easy and small size data in Application, when enter in page will invoke it to get data from which has been stored.

In Second Page Where you want to store data, writing as bellow:

Application.Current.Properties ["value"] = valuedata;

When back to First Page, override OnAppearing method to update UI:

protected override void OnAppearing()
{
    base.OnAppearing();
    if (Application.Current.Properties.ContainsKey("value"))
    {
        var ValueGet = Application.Current.Properties ["value"] as DataType;
        // do something with other things
    }
}

Note: ViewModel if want to dynamic update data , need to use INotifyPropertyChanged .

Sample Implementation:

public class ObservableProperty : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

ViewModelBase suggest implementing ICommand as a Dictionary structure like:

public abstract class ViewModelBase : ObservableProperty
{
    public Dictionary<string,ICommand> Commands { get; protected set; }

    public ViewModelBase()
    {
        Commands = new Dictionary<string,ICommand>();
    }
}

So all todo in your ViewModel is just inherit the ViewModelBase class and use it:

class LoginViewModel : ViewModelBase
{
    string userName;
    string password;

    public string UserName 
    {
         get {return userName;}
        set 
        {
            userName = value;
            OnPropertyChanged("UserName");
        }
     }

    public string Password 
    {
        get{return password;}
        set
        {
            password = value;
            OnPropertyChanged("Password");
        }
    }
    #endregion

    #region ctor
    public LoginViewModel()
    {
        //Add Commands
        Commands.Add("Login", new Command(CmdLogin));
    }
    #endregion


    #region UI methods

    private void CmdLogin()
    {
        // do your login jobs here
    }
    #endregion
}
0
ShawYu On

Solved.

MyViewModel (updated)

    public class MyViewModel : BaseViewModel
    {
        public ObservableCollection<obj> ObList1 { get; set; }
        public ObservableCollection<obj> ObList2 { get; set; }
        public ObservableCollection<obj> ObList3 { get; set; }

        private obj _selectedItem1 = new obj();
        public obj SelectedItem1 
        {
            get { return _selectedItem1; }

            //this is the line solved the problem
            //but still not understood thoroughly
            set { SetProperty(ref _selectedItem1, value); }
        }

        //same for _selectedItem2 _selectedItem3

    }

ps: BaseViewModel codes here (not changed, from template codes)

 public class BaseViewModel : INotifyPropertyChanged
    {
        //some other attributes
        //...

        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
    }
}

It seems that by calling SetProperty, OnPropertyChanged will also be revoked.

But still a little bit confusing about why the previous codes go like kind of "one-way" binding.