在MVVMCross中查看已实例化的ViewModel

本文关键字:实例化 ViewModel MVVMCross | 更新日期: 2023-09-27 18:25:09

有人知道如何查看现有的IMvxViewModel吗?

在我的应用程序中,我已经在另一个视图模型中创建了一堆ViewModel(PhotoViewModel)。它们作为父ViewModel(AlbumViewModel)上的属性存在。当我想查看时,只显示PhotoViewModel的一个特定实例,而不是创建该视图模型的一个新实例,这将是非常好的。

public class AlbumViewModel : MvxViewModel {
    public ObservableCollection<PhotoViewModel> Photos
    {
        get { return GetValue(() => Photos); }
        set { SetValue(value, () => Photos); }
    }
}
public class PhotoViewModel : MvxViewModel { }

我想知道除了创建自己的IMvxViewModelLocator之外,是否还有其他方法可以完成这项任务。我认为在MvxNavigationObject上有一个名为View的受保护方法,这对使用该框架的新开发人员和性能都非常有帮助。我们可以跳过当前完成的所有反射来实例化视图模型。

在MVVMCross中查看已实例化的ViewModel

MvvvmCross中的默认ShowViewModel机制使用基于页面的导航-此导航必须在WindowsPhone上使用Uri s,在Android上必须使用Intent s。

因此,MvvmCross不允许通过"丰富"对象进行导航-简单的可串行POCO可以,但不支持复杂的"丰富"物体。

由于"tombstoning",这一点更为重要——如果你的应用程序/页面/活动后来被重新水合,那么你就无法确定历史"back"堆栈中实际有哪些历史View或ViewModel对象。


如果您想按富对象导航,那么最好的方法是将这些富对象存储在查找服务中,然后按某个键/索引导航到查找中。然而,我个人将这些lookedup对象称为Models,而不是ViewModels(但边界有时会变得模糊!)


尽管基于MvvvmCross v1代码,但这个问题仍然提供了一个很好的背景——将对象传递到"的最佳方式是什么;导航到";MVVMCross中的视图模型?

一些最新的解释包括:

  • 如何使用mvvmcross在屏幕之间传递数据
  • v3中导航参数中的自定义类型
  • https://github.com/slodge/MvvmCross/wiki/ViewModel--to-ViewModel-navigation(在建)

最后一件事。。。。

MvvvmCross的宣言坚持Mvvvcross对定制非常开放。。。

正因为如此,如果你想的话,你可以覆盖MvvvmCross导航并查看模型位置。要做到这一点,创建自己的IMvxViewModelLocator可能是一个很好的开始方式。

经过一些测试,下面是一个建议的解决方案。我不是100%喜欢它,但它确实有效,并提供了我想要的类型开发人员体验。所以让我们深入研究。

首先,我的所有ViewModel(VM)都继承自一个基本VM AVM。这个抽象基类支持将对象查找为公共静态方法。这有点恶心,但如果你愿意喝Kool Aid,效果很好。下面是类中与此问题相关的部分:

public abstract class AVM : MvxViewModel {
    private static readonly Dictionary<Guid, WeakReference> ViewModelCache = new Dictionary<Guid, WeakReference>();
    private static readonly string BUNDLE_PARAM_ID = @"AVM_ID";
    private Guid AVM_ID = Guid.NewGuid();
    private Type MyType;
    protected AVM()
    {
        MyType = this.GetType();
        ViewModelCache.Add(AVM_ID, new WeakReference(this));
    }
    public static bool TryLoadFromBundle(IMvxBundle bundle, out IMvxViewModel viewModel)
    {
        if (null != bundle && bundle.Data.ContainsKey(BUNDLE_PARAM_ID))
        {
            var id = Guid.Parse(bundle.Data[BUNDLE_PARAM_ID]);
            viewModel = TryLoadFromCache(id);
            return true;
        }
        viewModel = null;
        return false;
    }
    private static IMvxViewModel TryLoadFromCache(Guid Id)
    {
        if (ViewModelCache.ContainsKey(Id))
        {
            try
            {
                var reference = ViewModelCache[Id];
                if (reference.IsAlive)
                    return (IMvxViewModel)reference.Target;
            }
            catch (Exception exp) { Mvx.Trace(exp.Message); }
        }
        return null;
    }

    protected void View()
    {
        var param = new Dictionary<string, string>();
        param.Add(BUNDLE_PARAM_ID, AVM_ID.ToString());
        ShowViewModel(MyType, param);
    }

为了将这一切连接起来,您必须创建一个自定义视图模型定位器。这是自定义定位器:

public class AVMLocator : MvxDefaultViewModelLocator
{
    public override bool TryLoad(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState, out IMvxViewModel viewModel)
    {
        if (AVM.TryLoadFromBundle(parameterValues, out viewModel))
            return true;
        return base.TryLoad(viewModelType, parameterValues, savedState, out viewModel);
    }
}

最后你必须把电线连接起来。要做到这一点,请进入App.cs并覆盖CreateDefaultViewModelLocator,如下所示:

    protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
    {
        return new AVMLocator();
    }

你已经准备好了。现在,在任何已经存在的派生ViewModel中,您都可以执行以下操作:

myDerivedVM.View();

我还需要做更多的工作(比如确保WeakReferences完成它们的工作,并且我没有内存泄漏和一些额外的错误处理),但至少这是我想要的体验。我做的最后一件事是将以下命令添加到AVM基类:

public MvxCommand ViewCommand
{
    get { return new MvxCommand(View); }
}

现在,您可以将该命令绑定到任何UI对象,当被调用时,它将使用VM的那个实例启动该视图。

斯图亚特,谢谢你的帮助,指引我朝着正确的方向前进。我很想听听你对我提供的解决方案的反馈。感谢您与MVVMCross的合作。这确实是一个非常漂亮的代码。

干杯。