MvvmCross - 视图模型 mvxInteraction 始终为 null
MvvmCross - View Model mvxInteraction is always null
我是 MvvmCross 和 Xamarin 的新手,也许我在这里遗漏了一些东西,但是当我尝试将一些数据从 ViewModel
传递到 View
时,就像这里描述的那样(几乎)
https://www.mvvmcross.com/documentation/fundamentals/mvxinteraction
但问题是 - 我在 View
中的 MvxInteraction
变量始终为 null,即使我向那里传递了字符串!
public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
{
get
{
return _interaction;
}
set
{
if (_interaction != null) // <-- Always NULL!
_interaction.Requested -= OnInteractionRequested;
_interaction = value;
_interaction.Requested += OnInteractionRequested;
}
}
视图模型代码:
public class MenuViewModel : MvxViewModel
{
private IDataScanner _dataScanner { get; set; }
//demo
public class YesNoQuestion
{
public string Question { get; set; }
}
private MvxInteraction<YesNoQuestion> _interaction = new MvxInteraction<YesNoQuestion>();
// need to expose it as a public property for binding (only IMvxInteraction is needed in the view)
public IMvxInteraction<YesNoQuestion> Interaction
{
get
{
return _interaction;
}
}
private void DoFinishProfileCommand()
{
// 1. do cool stuff with profile data
// ...
// 2. request interaction from view
// 3. execution continues in callbacks
var request = new YesNoQuestion
{
Question = "Do you want to save your profile?"
};
_interaction.Raise(request);
}
// demo
public class DataEventArgs : EventArgs
{
public List<string> DataList;
}
public delegate void ScanDoneEvent(object sender, DataEventArgs args);
public event ScanDoneEvent ScanDone;
protected virtual void OnScanDone(List<string> dataList)
{
ScanDone?.Invoke(this, new DataEventArgs { DataList = dataList });
}
public MenuViewModel(IDataScanner dataScanner)
{
_dataScanner = dataScanner;
RunScan();
}
private ObservableCollection<string> _filesCollection;
public ObservableCollection<string> FilesCollection
{
get { return _filesCollection; }
set
{
_filesCollection = value;
RaisePropertyChanged(() => FilesCollection);
}
}
private async void RunScan()
{
var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
FilesCollection = new ObservableCollection<string>(files);
DoFinishProfileCommand();
}
}
查看代码:
[Activity]
public class MenuView : MvxActivity
{
public MenuView()
{
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.menu_view);
}
//demo
private IMvxInteraction<MenuViewModel.YesNoQuestion> _interaction;
public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
{
get
{
return _interaction;
}
set
{
if (_interaction != null)// <-- Always NULL!
_interaction.Requested -= OnInteractionRequested;
_interaction = value;
_interaction.Requested += OnInteractionRequested;
}
}
private async void OnInteractionRequested(object sender, MvxValueEventArgs<MenuViewModel.YesNoQuestion> eventArgs)
{
var yesNoQuestion = eventArgs.Value.Question;
// show dialog
Toast.MakeText(this, yesNoQuestion, ToastLength.Long).Show();
}
}
UPD 09.11.2017
终于让它按照我想要的方式工作了。
我只是简单地使用服务而不是 MvxInteraction,正如 nmilcoff(顺便说一句,非常感谢您的回答)所建议的那样,当我从它调用 toast 时,将其包装在 runOnUiThread
中。
所以,最终代码在这里。
吐司服务:
public class ToastService : IToastService
{
private readonly IMvxAndroidCurrentTopActivity _topActivity;
public ToastService(IMvxAndroidCurrentTopActivity topActivity)
{
_topActivity = topActivity;
}
public void ShowToast(string message)
{
_topActivity.Activity.RunOnUiThread(
() => Toast.MakeText(_topActivity.Activity.ApplicationContext, message, ToastLength.Long).Show()
);
}
}
菜单视图模型:
public class MenuViewModel : MvxViewModel
{
private IDataScanner _dataScanner { get; set; }
private IToastService _toastService { get; set; }
public MenuViewModel(IDataScanner dataScanner, IToastService toastService)
{
_dataScanner = dataScanner;
_toastService = toastService;
}
public override void ViewAppeared()
{
base.ViewAppeared();
RunScan();
}
private ObservableCollection<string> _filesCollection;
public ObservableCollection<string> FilesCollection
{
get { return _filesCollection; }
set
{
_filesCollection = value;
// RaisePropertyChanged(() => FilesCollection);
}
}
private async void RunScan()
{
var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
FilesCollection = new ObservableCollection<string>(files);
// Someval = files[0];
_toastService.ShowToast(files[0]);
}
}
您可能会找到 StarWarsSample interesting, as it uses MvxInteraction。
如您在该应用中所见,您需要稍后在视图代码中声明绑定。您可以将 Fluent Binding 块移动到 Activity 的 OnCreate(Android.OS.Bundle bundle)
方法吗?像这样:
protected override void OnCreate(Android.OS.Bundle bundle)
{
base.OnCreate(bundle);
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
但这可能行不通,因为您在代码中过早地请求了 MvxInteraction。因此,我建议您改为使用依赖服务:
1) 在 Core 级别创建一个界面,使用显示 toast 的方法:
public interface IToastService
{
void ShowToast(string message);
}
2) 在平台级别创建服务的实现:
public class ToastService : IToastService
{
private readonly IMvxAndroidCurrentTopActivity _topActivity;
public ToastService(IMvxAndroidCurrentTopActivity topActivity)
{
_topActivity = topActivity;
}
public void ShowToast(string message)
{
Toast.MakeText(activity.ApplicationContext, message, ToastLength.Short).Show();
}
}
3) 向 IoC 容器注册您的服务。像这样的东西(在你的平台项目的设置 class 中):
protected override void InitializeLastChance()
{
base.InitializeLastChance();
Mvx.RegisterType<IToastService, ToastService>();
}
就是这样。您已准备好 inject/resolve 您的 IToastService 随处可用!
我是 MvvmCross 和 Xamarin 的新手,也许我在这里遗漏了一些东西,但是当我尝试将一些数据从 ViewModel
传递到 View
时,就像这里描述的那样(几乎)
https://www.mvvmcross.com/documentation/fundamentals/mvxinteraction
但问题是 - 我在 View
中的 MvxInteraction
变量始终为 null,即使我向那里传递了字符串!
public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
{
get
{
return _interaction;
}
set
{
if (_interaction != null) // <-- Always NULL!
_interaction.Requested -= OnInteractionRequested;
_interaction = value;
_interaction.Requested += OnInteractionRequested;
}
}
视图模型代码:
public class MenuViewModel : MvxViewModel
{
private IDataScanner _dataScanner { get; set; }
//demo
public class YesNoQuestion
{
public string Question { get; set; }
}
private MvxInteraction<YesNoQuestion> _interaction = new MvxInteraction<YesNoQuestion>();
// need to expose it as a public property for binding (only IMvxInteraction is needed in the view)
public IMvxInteraction<YesNoQuestion> Interaction
{
get
{
return _interaction;
}
}
private void DoFinishProfileCommand()
{
// 1. do cool stuff with profile data
// ...
// 2. request interaction from view
// 3. execution continues in callbacks
var request = new YesNoQuestion
{
Question = "Do you want to save your profile?"
};
_interaction.Raise(request);
}
// demo
public class DataEventArgs : EventArgs
{
public List<string> DataList;
}
public delegate void ScanDoneEvent(object sender, DataEventArgs args);
public event ScanDoneEvent ScanDone;
protected virtual void OnScanDone(List<string> dataList)
{
ScanDone?.Invoke(this, new DataEventArgs { DataList = dataList });
}
public MenuViewModel(IDataScanner dataScanner)
{
_dataScanner = dataScanner;
RunScan();
}
private ObservableCollection<string> _filesCollection;
public ObservableCollection<string> FilesCollection
{
get { return _filesCollection; }
set
{
_filesCollection = value;
RaisePropertyChanged(() => FilesCollection);
}
}
private async void RunScan()
{
var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
FilesCollection = new ObservableCollection<string>(files);
DoFinishProfileCommand();
}
}
查看代码:
[Activity]
public class MenuView : MvxActivity
{
public MenuView()
{
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
protected override void OnViewModelSet()
{
SetContentView(Resource.Layout.menu_view);
}
//demo
private IMvxInteraction<MenuViewModel.YesNoQuestion> _interaction;
public IMvxInteraction<MenuViewModel.YesNoQuestion> Interaction
{
get
{
return _interaction;
}
set
{
if (_interaction != null)// <-- Always NULL!
_interaction.Requested -= OnInteractionRequested;
_interaction = value;
_interaction.Requested += OnInteractionRequested;
}
}
private async void OnInteractionRequested(object sender, MvxValueEventArgs<MenuViewModel.YesNoQuestion> eventArgs)
{
var yesNoQuestion = eventArgs.Value.Question;
// show dialog
Toast.MakeText(this, yesNoQuestion, ToastLength.Long).Show();
}
}
UPD 09.11.2017
终于让它按照我想要的方式工作了。
我只是简单地使用服务而不是 MvxInteraction,正如 nmilcoff(顺便说一句,非常感谢您的回答)所建议的那样,当我从它调用 toast 时,将其包装在 runOnUiThread
中。
所以,最终代码在这里。
吐司服务:
public class ToastService : IToastService
{
private readonly IMvxAndroidCurrentTopActivity _topActivity;
public ToastService(IMvxAndroidCurrentTopActivity topActivity)
{
_topActivity = topActivity;
}
public void ShowToast(string message)
{
_topActivity.Activity.RunOnUiThread(
() => Toast.MakeText(_topActivity.Activity.ApplicationContext, message, ToastLength.Long).Show()
);
}
}
菜单视图模型:
public class MenuViewModel : MvxViewModel
{
private IDataScanner _dataScanner { get; set; }
private IToastService _toastService { get; set; }
public MenuViewModel(IDataScanner dataScanner, IToastService toastService)
{
_dataScanner = dataScanner;
_toastService = toastService;
}
public override void ViewAppeared()
{
base.ViewAppeared();
RunScan();
}
private ObservableCollection<string> _filesCollection;
public ObservableCollection<string> FilesCollection
{
get { return _filesCollection; }
set
{
_filesCollection = value;
// RaisePropertyChanged(() => FilesCollection);
}
}
private async void RunScan()
{
var files = await _dataScanner.GetDataListAsync().ConfigureAwait(false);
FilesCollection = new ObservableCollection<string>(files);
// Someval = files[0];
_toastService.ShowToast(files[0]);
}
}
您可能会找到 StarWarsSample interesting, as it uses MvxInteraction。
如您在该应用中所见,您需要稍后在视图代码中声明绑定。您可以将 Fluent Binding 块移动到 Activity 的 OnCreate(Android.OS.Bundle bundle)
方法吗?像这样:
protected override void OnCreate(Android.OS.Bundle bundle)
{
base.OnCreate(bundle);
var set = this.CreateBindingSet<MenuView, MenuViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
但这可能行不通,因为您在代码中过早地请求了 MvxInteraction。因此,我建议您改为使用依赖服务:
1) 在 Core 级别创建一个界面,使用显示 toast 的方法:
public interface IToastService
{
void ShowToast(string message);
}
2) 在平台级别创建服务的实现:
public class ToastService : IToastService
{
private readonly IMvxAndroidCurrentTopActivity _topActivity;
public ToastService(IMvxAndroidCurrentTopActivity topActivity)
{
_topActivity = topActivity;
}
public void ShowToast(string message)
{
Toast.MakeText(activity.ApplicationContext, message, ToastLength.Short).Show();
}
}
3) 向 IoC 容器注册您的服务。像这样的东西(在你的平台项目的设置 class 中):
protected override void InitializeLastChance()
{
base.InitializeLastChance();
Mvx.RegisterType<IToastService, ToastService>();
}
就是这样。您已准备好 inject/resolve 您的 IToastService 随处可用!