在Xamarin.Forms中,如何将同一个viewmodel的变化通知回上一页? (可以传递到第二页,但不能返回)
In Xamarin.Forms, how to notify the changes of the same viewmodel back to the previous page? (can pass to the second page, but not back)
我有两个页面,"HomePage","SettingPage",包括相同的 "MyView"(那里有一些选择器)。
当我从主页单击 "Go Setting"(或显示更多设置)按钮时,值会同步到设置页面。但是当我在设置页面上点击"Apply"时,数值没有返回。
我是 c# 和 Xamarin 的新手,尝试在线搜索和 Microsoft 文档。但是我找不到解决这个问题的方法。
我也在关注这个link:How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms?
并在我的代码中做了相同的全局值。
- 我的视图(内容视图)
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
}
- 首页(包括MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
- SettingPage(包括同一个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
}
- GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
- 视图模型
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>();
}
}
也许我应该将 SettingPage 上的更改通知给 viewmodel?或者在 viewmodel 的 "set" 中做一些事情?
令人困惑的一点是,两个页面使用相同的视图模型嵌入相同的视图,但仅通知从Page1到Page2的更改,而不是Page2到Page1的更改。
任何想法,提前谢谢。
方案一:
使用 Event 可以将值传回上一页。
在第二页定义事件:
public delegate void EventHandler(string status);
public event EventHandler EventPass;
页面消失时调用事件:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
在FirstPage中,当Naviagtion地方需要在此处添加Event时:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
现在值将传递到这里:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
方案二:
使用Properties Dictionary在应用程序中存储简单且体积小的数据,当进入页面时将调用它来获取已存储的数据。
在Second Page你要存放数据的地方,写成如下:
Application.Current.Properties ["value"] = valuedata;
返回第一页时,覆盖 OnAppearing 方法以更新 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
}
}
注意:ViewModel如果要动态更新数据,需要用到INotifyPropertyChanged.
示例实现:
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 建议将 ICommand 实现为字典结构,例如:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
因此,您的 ViewModel 中的所有待办事项都只是继承 ViewModelBase class 并使用它:
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
}
已解决。
MyViewModel(更新)
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 代码在这里(未更改,来自模板代码)
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
}
}
好像通过调用SetProperty,OnPropertyChanged也会被撤销
但对于为什么以前的代码像某种 "one-way" 绑定一样,仍然有点困惑。
我有两个页面,"HomePage","SettingPage",包括相同的 "MyView"(那里有一些选择器)。 当我从主页单击 "Go Setting"(或显示更多设置)按钮时,值会同步到设置页面。但是当我在设置页面上点击"Apply"时,数值没有返回。
我是 c# 和 Xamarin 的新手,尝试在线搜索和 Microsoft 文档。但是我找不到解决这个问题的方法。
我也在关注这个link:How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms? 并在我的代码中做了相同的全局值。
- 我的视图(内容视图)
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
}
- 首页(包括MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
- SettingPage(包括同一个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
}
- GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
- 视图模型
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>();
}
}
也许我应该将 SettingPage 上的更改通知给 viewmodel?或者在 viewmodel 的 "set" 中做一些事情?
令人困惑的一点是,两个页面使用相同的视图模型嵌入相同的视图,但仅通知从Page1到Page2的更改,而不是Page2到Page1的更改。
任何想法,提前谢谢。
方案一:
使用 Event 可以将值传回上一页。
在第二页定义事件:
public delegate void EventHandler(string status);
public event EventHandler EventPass;
页面消失时调用事件:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
在FirstPage中,当Naviagtion地方需要在此处添加Event时:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
现在值将传递到这里:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
方案二:
使用Properties Dictionary在应用程序中存储简单且体积小的数据,当进入页面时将调用它来获取已存储的数据。
在Second Page你要存放数据的地方,写成如下:
Application.Current.Properties ["value"] = valuedata;
返回第一页时,覆盖 OnAppearing 方法以更新 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
}
}
注意:ViewModel如果要动态更新数据,需要用到INotifyPropertyChanged.
示例实现:
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 建议将 ICommand 实现为字典结构,例如:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
因此,您的 ViewModel 中的所有待办事项都只是继承 ViewModelBase class 并使用它:
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
}
已解决。
MyViewModel(更新)
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 代码在这里(未更改,来自模板代码)
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
}
}
好像通过调用SetProperty,OnPropertyChanged也会被撤销
但对于为什么以前的代码像某种 "one-way" 绑定一样,仍然有点困惑。