【问题标题】:Can I implement my own view resolution service and have RequestNavigate use it?我可以实现自己的视图解析服务并让 RequestNavigate 使用它吗?
【发布时间】:2011-11-19 04:05:09
【问题描述】:

我是 Prism 的新手,我目前正在使用 Prism 作为概念验证项目重写我们现有的应用程序之一。

应用程序使用 MVVM 和 ViewModel 优先方法:我们的 ViewModel 由容器解析,IViewResolver 服务确定它应该连接到哪个视图(使用名称约定等)。

目前的代码(将视图添加到选项卡控件)如下所示:

var vm = (get ViewModel from somewhere)
IRegion reg = _regionManager.Regions["MainRegion"];
var vw = _viewResolver.FromViewModel(vm); // Spins up a view and sets its DataContext
reg.Add(vw);
reg.Activate(vw);

这一切都很好,但是我真的很想使用 Prism 导航框架来为我做所有这些事情,以便我可以做这样的事情:

_regionManager.RequestNavigate(
    "MainRegion", 
    new Uri("NameOfMyViewModel", UriKind.Relative)
);

让 Prism 启动 ViewModel + View,设置 DataContext 并将视图插入到区域中。

我通过创建引用 ViewModel 类型的 DataTemplates 取得了一些成功,例如:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Module01">
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestView />
</DataTemplate>
</ResourceDictionary>

...并让模块在初始化模块时将相关资源字典添加到应用程序资源中,但这似乎有点垃圾。

有没有办法有效地从 Prism 接管视图创建,以便在调用 RequestNavigate 时,我可以查看提供的 Uri 并基于此启动视图/视图模型? RegionManager.RegisterViewWithRegion 的重载需要一个委托,允许您自己提供视图,我想我正在追求类似的东西。

我想我可能需要提供自己的 IRegionBehaviorFactory,但我不确定所涉及的内容(或者即使我走在正确的道路上!)。

任何帮助表示赞赏!

-- 注意:最初发布于the prism codeplex site

【问题讨论】:

    标签: wpf mvvm prism prism-4


    【解决方案1】:

    当然可以。我发现 Prism v4 确实是可扩展的,只要您知道在哪里插入即可。

    在这种情况下,您需要自己的 IRegionNavigationContentLoader 自定义实现。

    以下是在引导程序中进行设置的方法(示例来自我自己的一个项目的 UnityBootstrapper 的子类):

    protected override void ConfigureContainer()
    {
        // IMPORTANT: Due to the inner workings of UnityBootstrapper, accessing
        // ServiceLocator.Current here will throw an exception!
        // If you want access to IServiceLocator, resolve it from the container directly.
        base.ConfigureContainer();
    
        // Set up our own content loader, passing it a reference to the service locator
        // (it will need this to resolve ViewModels from the container automatically)
        this.Container.RegisterInstance<IRegionNavigationContentLoader>(
           new ViewModelContentLoader(this.Container.Resolve<IServiceLocator>()));
    }
    

    ViewModelContentLoader 本身派生自 RegionNavigationContentLoader 以重用代码,看起来像这样:

    public class ViewModelContentLoader : RegionNavigationContentLoader
    {
        private readonly IServiceLocator serviceLocator;
    
        public ViewModelContentLoader(IServiceLocator serviceLocator)
            : base(serviceLocator)
        {
            this.serviceLocator = serviceLocator;
        }
    
        // THIS IS CALLED WHEN A NEW VIEW NEEDS TO BE CREATED
        // TO SATISFY A NAVIGATION REQUEST
        protected override object CreateNewRegionItem(string candidateTargetContract)
        {
            // candidateTargetContract is e.g. "NameOfMyViewModel"
    
            // Just a suggestion, plug in your own resolution code as you see fit
            var viewModelType = this.GetTypeFromName(candidateTargetContract);
            var viewModel = this.serviceLocator.GetInstance(viewModelType);
    
            // get ref to viewResolver somehow -- perhaps from the container?
            var view = _viewResolver.FromViewModel(vm);
    
            return view;
        }
    
        // THIS IS CALLED TO DETERMINE IF THERE IS ANY EXISTING VIEW
        // THAT CAN SATISFY A NAVIGATION REQUEST
        protected override IEnumerable<object> 
        GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
        {
            if (region == null) {
                throw new ArgumentNullException("region");
            }
    
            // Just a suggestion, plug in your own resolution code as you see fit
            var viewModelType = this.GetTypeFromName(candidateNavigationContract);
    
            return region.Views.Where(v =>
                ViewHasDataContract((FrameworkElement)v, viewModelType) ||
                string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
                string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
        }
    
        // USED IN MY IMPLEMENTATION OF GetCandidatesFromRegion
        private static bool 
        ViewHasDataContract(FrameworkElement view, Type viewModelType)
        {
            var dataContextType = view.DataContext.GetType();
    
            return viewModelType.IsInterface
               ? dataContextType.Implements(viewModelType)
               : dataContextType == viewModelType 
                       || dataContextType.GetAncestors().Any(t => t == viewModelType);
        }
    
        // USED TO MAP STRINGS OF VIEWMODEL TYPE NAMES TO ACTUAL TYPES
        private Type GetTypeFromName(string typeName)
        {
            // here you need to map the string type to a Type object, e.g.
            // "NameOfMyViewModel" => typeof(NameOfMyViewModel)
    
            return typeof(NameOfMyViewModel); // hardcoded for simplicity
        }
    }
    

    【讨论】:

    • 这真的很完美!谢谢! - Pj
    【解决方案2】:

    为了避免对“ViewModel 优先方法”的一些混淆: 您更多地使用“控制器方法”,但没有使用“ViewModel 优先方法”。 “ViewModel 第一种方法”是,当您在 ViewModel 中注入 View,但是通过第三方组件(控制器)连接 ViewModel 和 View 时,顺便说一下(我不想说“最好的”,但是)最松耦合的方法。

    但要回答您的问题: 一个可能的解决方案是为 Prism RegionManager 编写一个扩展,它完全按照您上面描述的方式:

        public static class RegionManagerExtensions
        {            
            public static void AddToRegion<TViewModel>(
                   this IRegionManager regionManager, string region)
            {
                var viewModel = ServiceLocator.Current.GetInstance<TViewModel>();
                FrameworkElement view;
    
                // Get View depending on your conventions
    
                if (view == null) throw new NullReferenceException("View not found.");
    
                view.DataContext = viewModel;
                regionManager.AddToRegion(region, view);
                regionManager.Regions[region].Activate(view);
    
            }
        }
    

    那么你可以这样调用这个方法:

    regionManager.AddToRegion<IMyViewModel>("MyRegion");
    

    【讨论】:

    • 这不正是我在原始示例中所做的吗? - 我追求的是一种覆盖/影响导航api(即RequestNavigate())如何从Uri解析视图实例的方法
    • 我稍微编辑了这个问题,希望能更清楚!
    猜你喜欢
    • 2014-01-19
    • 1970-01-01
    • 2020-07-27
    • 1970-01-01
    • 1970-01-01
    • 2019-11-27
    • 2017-06-05
    • 2017-09-01
    • 2015-05-19
    相关资源
    最近更新 更多