从视图模型导航到mvvmcross

时间:2018-01-14 17:28:35

标签: c# xamarin mvvm xamarin.ios mvvmcross

我想使用mvvmcross中的viewmodel从一个屏幕移动到另一个屏幕,并将一些模型传递给下一个ViewModel。但是我遇到了崩溃:

  

MvvmCross.Platform.Exceptions.MvxException:无法从定位器MvxDefaultViewModelLocator为类型iManage.ViewModels.LoginViewModel构造和初始化ViewModel - 检查InnerException以获取更多信息---> MvvmCross.Platform.Exceptions.MvxException:创建LoginViewModel类型的viewModel时出现问题---> MvvmCross.Platform.Exceptions.MvxIoCResolveException:创建iManage.ViewModels.LoginViewModel时无法解析类型为SchoolModel的参数项的参数     在MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues(System.Type type,System.Reflection.ConstructorInfo firstConstructor)[0x00066] in< 6adc0d5857264558a9d45778a78ae02a&gt ;:0     在MvvmCross.Platform.IoC.MvxSimpleIoCContainer.IoCConstruct(System.Type type)[0x0002c] in< 6adc0d5857264558a9d45778a78ae02a&gt ;:0     在MvvmCross.Platform.Mvx.IocConstruct(System.Type t)[0x00006] in< 6adc0d5857264558a9d45778a78ae02a&gt ;:0     在MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load(System.Type viewModelType,MvvmCross.Core.ViewModels.IMvxBundle parameterValues,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00000] in:0     ---内部异常堆栈跟踪结束---     在MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load(System.Type viewModelType,MvvmCross.Core.ViewModels.IMvxBundle parameterValues,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00029] in:0     在MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel(MvvmCross.Core.ViewModels.MvxViewModelRequest请求,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00035] in:0     ---内部异常堆栈跟踪结束---     在MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel(MvvmCross.Core.ViewModels.MvxViewModelRequest请求,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00068] in:0     在MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.LoadViewModel(MvvmCross.iOS.Views.IMvxIosView iosView)[0x0005f] in< 6f99728979034e579bc72f6d53e5bc35>:0     在MvvmCross.Core.Views.MvxViewExtensionMethods.OnViewCreate(MvvmCross.Core.Views.IMvxView视图,System.Func`1 [TResult] viewModelLoader)[0x00012] in:0     在MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.OnViewCreate(MvvmCross.iOS.Views.IMvxIosView iosView)[0x00001] in< 6f99728979034e579bc72f6d53e5bc35>:0     在MvvmCross.iOS.Views.MvxViewControllerAdapter.HandleViewDidLoadCalled(System.Object sender,System.EventArgs e)[0x00007] in< 6f99728979034e579bc72f6d53e5bc35&gt ;:0     at at(wrapper delegate-invoke):invoke_void_object_EventArgs(object,System.EventArgs)     在MvvmCross.Platform.Core.MvxDelegateExtensionMethods.Raise(System.EventHandler eventHandler,System.Object sender)[0x00003] in< 6adc0d5857264558a9d45778a78ae02a&gt ;:0     在MvvmCross.Platform.iOS.Views.MvxEventSourceViewController.ViewDidLoad()[0x00006] in< 4467c42ffcc4478e847227b8e4af47fe>:0     在MvvmCross.iOS.Views.MvxViewController.ViewDidLoad()[0x00001] in< 6f99728979034e579bc72f6d53e5bc35>:0     at iManage.iOS.Views.LoginView.ViewDidLoad()[0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Views/LoginView.cs:18     at at(wrapper managed-to-native)UIKit.UIApplication:UIApplicationMain(int,string [],intptr,intptr)     在UIKit.UIApplication.Main(System.String [] args,System.IntPtr principal,System.IntPtr委托)[0x00005]在/ Users / builder / data / lanes / 5665 / f70a1348 / source / xamarin-macios / src / UIKit中/UIApplication.cs:79     在/ Users / builder / data / lanes / 5665 / f70a1348 / source / xamarin-macios / src / UIKit中的UIKit.UIApplication.Main(System.String [] args,System.String principalClassName,System.String delegateClassName)[0x00038] /UIApplication.cs:63     at iManage.iOS.Application.Main(System.String [] args)[0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

来自ViewModel代码:

public class SchoolSelectionViewModel : BaseViewModel
{
    private readonly ISchoolNames _schoolService;
    public SchoolSelectionViewModel(ISchoolNames schoolService)
    {
        _schoolService = schoolService;
    }
    public override void Start()
    {
        IsLoading = true;
        _schoolService.GetFeedItems(OnDilbertItems, OnError);
    }

    private void OnDilbertItems(List<SchoolModel> list)
    {
        IsLoading = false;
        Items = list;
    }

    private void OnError(Exception error)
    {
        // not reported for now
        IsLoading = false;
    }

    private List<SchoolModel> _items = new List<SchoolModel>();
    public List<SchoolModel> Items
    {
        get { return _items; }
        set { _items = value; RaisePropertyChanged(() => Items); }
    }
    private MvxCommand<SchoolModel> _itemSelectedCommand;
    public ICommand ItemSelectedCommand
    {
        get
        {
            _itemSelectedCommand = _itemSelectedCommand ?? new MvvmCross.Core.ViewModels.MvxCommand<SchoolModel>(DoSelectItem);
            return _itemSelectedCommand;
        }
    }

    private void DoSelectItem(SchoolModel item)
    {
        //ShowViewModel<LoginViewModel>(item);
        ShowViewModel<LoginViewModel>(new LoginViewModel(item));
    }
}

To ViewModel Code:

public class LoginViewModel : BaseViewModel
{
    private readonly ILoginService _loginService;

    private readonly IDialogService _dialogService;

    public LoginViewModel(SchoolModel item)
    {
        //_loginService = loginService;
        //_dialogService = dialogService;
        School = item;
        Username = "TestUser";
        Password = "YouCantSeeMe";
        IsLoading = false;
    }

    private SchoolModel _school;
    public SchoolModel School
    {
        get
        {
            return _school;
        }

        set
        {
            SetProperty(ref _school, value);
            RaisePropertyChanged(() => School);
        }
    }

    private string _username;
    public string Username
    {
        get
        {
            return _username;
        }

        set
        {
            SetProperty(ref _username, value);
            RaisePropertyChanged(() => Username);
        }
    }

    private string _password;
    public string Password
    {
        get
        {
            return _password;
        }

        set
        {
            SetProperty(ref _password, value);
            RaisePropertyChanged(() => Password);
        }
    }

    private IMvxCommand _loginCommand;
    public virtual IMvxCommand LoginCommand
    {
        get
        {
            _loginCommand = _loginCommand ?? new MvxCommand(AttemptLogin, CanExecuteLogin);
            return _loginCommand;
        }
    }

    private void AttemptLogin()
    {
        if (_loginService.Login(Username, Password))
        {
            ShowViewModel<DashboardStdViewModel>();
        }
        else
        {
            _dialogService.Alert("We were unable to log you in!", "Login Failed", "OK");
        }
    }

    private bool CanExecuteLogin()
    {
        return (!string.IsNullOrEmpty(Username) || !string.IsNullOrWhiteSpace(Username))
               && (!string.IsNullOrEmpty(Password) || !string.IsNullOrWhiteSpace(Password));
    }
}

EDIT1: 我在ViewModel中修改了以下内容:

        private async void DoSelectItem(SchoolModel item)
    {
        await _navigationService.Navigate<LoginViewModel,SchoolModel>(item);
    }

将下一个ViewModel声明更改为:

public class LoginViewModel : MvxViewModel<SchoolModel>

当我尝试显示以下错误时显示下一个viewmodel时仍然崩溃: 对象引用未设置为对象的实例 的 EDIT2: 完成错误:

  

System.NullReferenceException:未将对象引用设置为对象的实例     at iManage.ViewModels.SchoolSelectionViewModel + d__19.MoveNext()[0x0000f] in /Users/pankajsachdeva/Projects/iManage/iManage/ViewModels/SchoolSelectionViewModel.cs:67     在/Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices中的System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()[0x0000c]中/exceptionservicescommon.cs:152     在/Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class中的System.Runtime.CompilerServices.AsyncMethodBuilderCore +&lt;&gt; c.b__6_0(System.Object state)[0x00000] /referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1018     在UIKit.UIKitSynchronizationContext + c__AnonStorey0。&lt;&gt; m__0()[0x00000] /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIKitSynchronizationContext.cs:24     在Foundation.NSAsyncActionDispatcher.Apply()[0x00000] /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/Foundation/NSAction.cs:163     at at(wrapper managed-to-native)UIKit.UIApplication:UIApplicationMain(int,string [],intptr,intptr)     在UIKit.UIApplication.Main(System.String [] args,System.IntPtr principal,System.IntPtr委托)[0x00005]在/ Users / builder / data / lanes / 5665 / f70a1348 / source / xamarin-macios / src / UIKit中/UIApplication.cs:79     在/ Users / builder / data / lanes / 5665 / f70a1348 / source / xamarin-macios / src / UIKit中的UIKit.UIApplication.Main(System.String [] args,System.String principalClassName,System.String delegateClassName)[0x00038] /UIApplication.cs:63     at iManage.iOS.Application.Main(System.String [] args)[0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

1 个答案:

答案 0 :(得分:2)

  

MvvmCross.Platform.Exceptions.MvxIoCResolveException:在MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues(System.Type类型)创建iManage.ViewModels.LoginViewModel时,无法解析 SchoolModel 类型的参数项的参数,System.Reflection.ConstructorInfo firstConstructor)

问题在于SchoolModel的构造函数中使用的参数LoginViewModel的类型。尝试构建MvxSimpleIoCContainerLoginViewModel无法解析类型。

所以我将讨论两个项目

  1. 通过查看MvvmCross如何构建视图模型,为什么会出现此异常。
  2. 如何使用navigation service introduced in MvvmCross 5和MvvmCross 5生命周期的传统ShowViewModel在视图模型之间传递参数。
  3. 在MvvmCross中查看模型IoC使用

    MvvmCross view models are constructed via IoC。这意味着视图模型类的公共构造函数的参数需要在IoC容器中注册,以便容器构造类。

    用作构造参数的类型也很重要,因为默认情况下,MvvmCross使用仅针对实现类型注册的Service Locator pattern支持接口类型。注册实现类型Foo和接口类型IFoo的示例。

    Mvx.ConstructAndRegisterSingleton<IFoo, Foo>();
    

    由于MvvmCross对视图模型使用构造函数注入,因此您无法使用视图模型类的公共构造函数将自己的参数传递给视图模型类实例。构造函数参数必须能够来自IoC容器。

    MvvmCross 5在视图模型之间传递参数(导航服务

    MvvmCross 5 the Navigation Service中引入了在MvvmCross中编写导航逻辑的首选方法。导航服务还提供了一种在视图模型之间传递参数的方法,以避免通过视图模型构造函数传递。 MvvmCross documentation中有一个很好的例子,展示了如何传递参数。这是一个摘录

    public class MyViewModel : MvxViewModel
    {
        private readonly IMvxNavigationService _navigationService;
    
        public MyViewModel(IMvxNavigationService navigationService)
        {
            _navigationService = navigationService;
        }
    
        public async Task SomeMethod()
        {
            await _navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
        }
    }
    
    public class NextViewModel : MvxViewModel<MyObject>
    {
        private MyObject _myObject;
    
        public override void Prepare(MyObject parameter)
        {
            // receive and store the parameter here
            _myObject = parameter;
        }
    }
    

    快速解释。从视图模型导航到的NextViewModel开始,您需要指定在继承的MvxViewModel基类泛型中传递的参数的类型,然后覆盖Prepare以传递参数。在MyViewModel方法的调用类navigate中,您可以包含导航到的视图模型的类型以及要传递的参数的类型。然后将参数作为navigate方法的第一个参数传递。

    在视图模型( ShowViewModel

    之间传递参数

    在您的示例代码中,您仍然使用较旧的ShowViewModel模式进行导航,因此我将介绍如何使用ShowViewModel方法传递参数。可以在here for the complex parameter objecthere for the simple parameter object找到ShowViewModel传递参数的MvvmCross文档。

    复杂参数对象

    对于复杂的参数对象,MvvmCross要求您安装了MvvmCross Json插件。此方法将序列化和反序列化通过

    传递的参数
    public class SchoolSelectionViewModel : BaseViewModel
    {
        public void DoSelectItem(SchoolModel item)
        {
            ShowViewModel<LoginViewModel, SchoolModel>(item);
        }
    }
    
    public class LoginViewModel : BaseViewModel<SchoolModel>
    {
        private SchoolModel _parameter;
    
        public override Task Prepare(SchoolModel parameter)
        {
            // receive and store the parameter here
            _parameter = parameter;
        }
    }
    

    如果您无法IMvxViewModel<TParameter>继承LoginViewModel,则可能还需要使用MvxViewModel<TParameter>合同实现自定义基类。

    public abstract class BaseViewModel<TParameter> : BaseViewModel, IMvxViewModel<TParameter>
    {
        public async Task Init(string parameter)
        {
            if (!string.IsNullOrEmpty(parameter))
            {
                IMvxJsonConverter serializer;
                if (!Mvx.TryResolve(out serializer))
                {
                    throw new MvxIoCResolveException("There is no implementation of IMvxJsonConverter registered. You need to use the MvvmCross Json plugin or create your own implementation of IMvxJsonConverter.");
                }
    
                var deserialized = serializer.DeserializeObject<TParameter>(parameter);
                Prepare(deserialized);
                await Initialize();
            }
        }
    
        public abstract void Prepare(TParameter parameter);
    }
    

    简单参数对象

    对于不需要序列化的基本类型,可以使用此方法,但代价是丢失类型安全参数。

    public class SchoolSelectionViewModel : BaseViewModel
    {
        public void DoSelectItem(SchoolModel item)
        {
            ShowViewModel<LoginViewModel>(item);
        }
    }
    
    public class LoginViewModel : BaseViewModel
    {
        private SchoolModel _parameter;
    
        public override void Init(SchoolModel parameter)
        {
            // receive and store the parameter here
            _parameter = parameter;
        }
    }
    

    注意 简单参数对象一起使用的类型的规则

    此处使用的课程必须是“简单”。仅用于这些导航的类:

    • 它必须包含无参数构造函数
    • 它应仅包含getset访问
    • 的公共属性
    • 这些属性应仅包含以下类型:
      • bool
      • 整数类型:sbyteshortintlongbyteushortuint,{{ 1}}
      • 浮点类型:ulongfloat
      • double
      • decimal
      • char
      • string
      • DateTime
      • 枚举值

    最后的注释

    对早期版本的MvvmCross v5.x.x中的模型生命周期进行了一些更改,但从v5.5+开始,上述模式应该适用。