在页面之间切换时,将始终重新创建ViewModel实例

时间:2016-02-03 10:33:20

标签: unity-container prism uwp

我正在使用Prism 6,UWP和Unity。

ViewModels将自动注入页面的datacontext。但是,当我在页面之间导航时,将始终重新创建视图模型。 Prism和Unity是否需要这种行为?

想象一下以下场景,用户在页面中输入一些数据,因此将设置viewmodel的正确属性。当用户切换回另一个页面并重新访问该页面时,所有输入的数据都将丢失,因为会创建一个新的viewmodel实例。

目前,我的解决方法是使用SessionStateService手册覆盖OnNavigatedTo和OnNavigatingFrom以保存viewmodel的所有属性。我不确定这是不是正确的方法?

您可以使用以下示例重现此行为: https://github.com/PrismLibrary/Prism-Samples-Windows/tree/master/SplitViewSample/SplitViewSample

2 个答案:

答案 0 :(得分:1)

我没有使用Prism,我正在使用模板10的修改版本。 我只是快速浏览了Prism源代码。看起来模板10借用了Prism的很多想法。

我会尝试从两个角度回答你的问题:

1)AFAIK,在Prism中有一个静态类,您可以使用该类设置在自动查找相应视图时如何创建/解析视图模型。该类为ViewModelLocationProvider,在文件ViewModelLocationProvider.cs中,您可以使用以下方法设置“视图模型工厂”

    /// <summary>
    /// Sets the default view model factory.
    /// </summary>
    /// <param name="viewModelFactory">The view model factory which provides the ViewModel type as a parameter.</param>
    public static void SetDefaultViewModelFactory(Func<Type, object> viewModelFactory)
    {
        _defaultViewModelFactory = viewModelFactory;
    }

    /// <summary>
    /// Sets the default view model factory.
    /// </summary>
    /// <param name="viewModelFactory">The view model factory that provides the View instance and ViewModel type as parameters.</param>
    public static void SetDefaultViewModelFactory(Func<object, Type, object> viewModelFactory)
    {
        _defaultViewModelFactoryWithViewParameter = viewModelFactory;
    }

    /// <summary>
    /// Registers the view model factory for the specified view type name.
    /// </summary>
    /// <param name="viewTypeName">The name of the view type.</param>
    /// <param name="factory">The viewmodel factory.</param>
    public static void Register(string viewTypeName, Func<object> factory)
    {
        _factories[viewTypeName] = factory;
    }

然后获取视图模型实例的所有逻辑如下,注意这里的注释摘要,它描述了逻辑/策略

    /// <summary>
    /// Automatically looks up the viewmodel that corresponds to the current view, using two strategies:
    /// It first looks to see if there is a mapping registered for that view, if not it will fallback to the convention based approach.
    /// </summary>
    /// <param name="view">The dependency object, typically a view.</param>
    /// <param name="setDataContextCallback">The call back to use to create the binding between the View and ViewModel</param>
    public static void AutoWireViewModelChanged(object view, Action<object, object> setDataContextCallback)
    {
        // Try mappings first
        object viewModel = GetViewModelForView(view);

        // Fallback to convention based
        if (viewModel == null)
        {
            var viewModelType = _defaultViewTypeToViewModelTypeResolver(view.GetType());
            if (viewModelType == null) 
                return;

            viewModel = _defaultViewModelFactoryWithViewParameter != null ? _defaultViewModelFactoryWithViewParameter(view, viewModelType) : _defaultViewModelFactory(viewModelType);
        }

        setDataContextCallback(view, viewModel);
    }

在第87和96行,您可以查看相应视图的模型实例。

这意味着,如果您不调用任何这些方法来设置工厂,它将回退到默认工厂

    /// <summary>
    /// The default view model factory whic provides the ViewModel type as a parameter.
    /// </summary>
    static Func<Type, object> _defaultViewModelFactory = type => Activator.CreateInstance(type);

很明显,你总会得到一个新的实例。

关于Unity,我没有看到任何特别的东西,唯一的线索就是在PrismApplication.csPrismApplication课程中,它设置工厂如下:

    /// <summary>
    /// Configures the <see cref="ViewModelLocator"/> used by Prism.
    /// </summary>
    protected virtual void ConfigureViewModelLocator()
    {
        ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Resolve(type));
    }

这意味着工厂正在使用

    /// <summary>
    /// Resolves the specified type.
    /// </summary>
    /// <param name="type">The type.</param>
    /// <returns>A concrete instance of the specified type.</returns>
    protected virtual object Resolve(Type type)
    {
        return Activator.CreateInstance(type);
    }

您可以使用自己的实现覆盖它。

PrismUnityApplicationPrismUnityApplication.cs中,它提供了使用Unity解析实例的默认实现

    /// <summary>
    /// Implements the Resolves method to be handled by the Unity Container.
    /// Use the container to resolve types (e.g. ViewModels and Flyouts)
    /// so their dependencies get injected
    /// </summary>
    /// <param name="type">The type.</param>
    /// <returns>A concrete instance of the specified type.</returns>
    protected override object Resolve(Type type)
    {
        return Container.Resolve(type);
    }

是的,就像其他人提到的那样,你可以通过Unity自己控制视图模型的生命周期。

2)对不起,答案很长, 但我觉得最好向你展示一些能让事情变得清晰的代码。 我会保持第二个短。

在我看来,当您的视图消失时,您不需要视图模型。 我不确定如何在UWP中实现Frame堆栈以及它们如何管理视图/页面实例。我假设一旦你导航到另一个页面,之前的视图/页面应该被释放或者可以在GC上发布,你有参数和页面类型能够导航回来,但它将是一个新的实例,你通过恢复视图模型来恢复视图状态。

所以,我觉得你走在了正确的轨道上。并且您应该尽可能地保存/保留您的用户数据,并且当应用程序暂停然后恢复时您的解决方案可以正常工作,您仍然可以恢复视图的状态。

感谢阅读。

答案 1 :(得分:0)

在UnityContainer中将ViewModel注册为单例(ContainerControlledLifetimeManager)时,应该解决此问题。最佳位置是方法App.xaml.cs

中的OnInitializeAsync
protected override Task OnInitializeAsync(IActivatedEventArgs args)
{
    Container.RegisterType<MyViewModel>(new ContainerControlledLifetimeManager());

    // rest of the method
}