3

我正在尝试将我的视图的 DataContexts 连接到来自另一个分离的程序集的视图模型。

Brian Lagunas在他的博客上写了一些关于Prism 的新 ViewModelLocator 入门的内容,但是,他的解决方案是专门定制约定以允许 ViewModelLocator 解析视图模型类型。

我的场景:

我有主项目(MyApplication.exe)包含引导程序、Shell 和视图在另一个分离的程序集(MyApplication.Process.dll)中我有所有的视图模型。

根据布赖恩的解释,我尝试了以下解决方案:

protected override void ConfigureViewModelLocator()
    {
        base.ConfigureViewModelLocator();

        ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType =>
                {
                    var viewName = viewType.FullName;
                    var viewAssemblyName = viewType.Assembly.GetName().Name;

                    var viewModelNameSuffix = viewName.EndsWith("View") ? "Model" : "ViewModel";

                    var viewModelName = viewName.Replace("Views", "ViewModels") + viewModelNameSuffix;
                    viewModelName = viewModelName.Replace(viewAssemblyName, viewAssemblyName + ".Process");
                    var viewModelAssemblyName = viewAssemblyName + ".Process";
                    var viewModelTypeName = string.Format(
                        CultureInfo.InvariantCulture,
                        "{0}, {1}",
                        viewModelName,
                        viewModelAssemblyName);

                    return Type.GetType(viewModelTypeName);                        
                });
    }

上面的解决方案可以正常工作,但是,我不知道这是否是最好的方法?

我想要的只是告诉 Prism ViewModelLocator 它必须在哪些程序集中找到视图模型,我的意思是与 Caliburn.Micro 相同的方法(在所有已注册的程序集中查找视图模型)。

例如,如果我的应用程序支持 Prism Modularity,如果程序集名称不以单词“ Process ”结尾,则上述解决方案将不起作用?

你对我有什么建议?

4

3 回答 3

2

您解析视图模型类型的代码当然没问题。如果您查看 Prism 的代码库,您会注意到使用次要反射和字符串替换的非常相似的方式。

static Func<Type, Type> _defaultViewTypeToViewModelTypeResolver =
        viewType =>
        {
            var viewName = viewType.FullName;
            viewName = viewName.Replace(".Views.", ".ViewModels.");
            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
            var viewModelName = String.Format(CultureInfo.InvariantCulture, "{0}{1}, {2}", viewName, suffix, viewAssemblyName);
            return Type.GetType(viewModelName);
        };

基于约定的类型解析的问题是您必须使用默认约定或创建自己的约定并坚持选择的约定。这意味着如果在您的第​​一个模块中您选择将视图模型放在一个.Process程序集中,那么您应该为所有其他模块执行此操作。坚持你的约定是最简单的方法。

好消息是:如果您不喜欢基于约定的解决方案,您可以像您已经做过的那样覆盖该解决方案并实现您喜欢的任何类型的解决方案,您希望它有多复杂。没有什么可以阻止您,例如,保留将视图映射到视图模型的字典(我们实际上是为一个项目所做的)。填写这个字典(或设置另一种解析方式)将在ModuleCatalog每个模块中完成。

于 2015-12-30T19:23:21.370 回答
1

这个解决方法怎么样?- 我真的放弃了编辑 ViewModelLocator - 在同一个项目中创建一个 ViewModule 并让它从另一个程序集中的另一个 ViewModel 继承,核心实现在基本 ViewModel 中,你仍然可以绑定到它并做你想做的一切。

我将 Base 类中的所有功能都标记为虚拟功能,因此如果我想使用某些特定于平台的组件,我可以扩展它们的功能。IRegionManager
这是来自平台项目( WPF )的代码

namespace PrismApplicationTest0.ViewModels
{
    public class ViewAViewModel : ViewAViewModelBase
    {
        private readonly IRegionManager _regionManager;

        public ViewAViewModel(IEventAggregator eventAggregator,IRegionManager regionManager) : base(eventAggregator)
        {
            _regionManager = regionManager;
        }

        protected override void UpdateMethod()
        {
            // After completing the core functionality
            base.UpdateMethod();

            // Switch to another page using platform specific region manager
            _regionManager.RequestNavigate(RegionNames.ContentRegion,"ViewB");
        }
      }
}

这是来自 PCL(可移植类库)的代码

using System;
using Prism.Commands;
using Prism.Events;
using Prism.Mvvm;

namespace MainModule.ViewModels
{
    public abstract class ViewAViewModelBase : BindableBase
    {
        private readonly IEventAggregator _eventAggregator;
        private string _firstName;
        private string _lastName;
        private DateTime? _lastUpdated;

        public string FirstName
        {
            get { return _firstName; }
            set { SetProperty(ref _firstName, value); }
        }

        public string LastName
        {
            get { return _lastName; }
            set { SetProperty(ref _lastName, value); }
        }

        public DateTime? LastUpdated
        {
            get { return _lastUpdated; }
            set { SetProperty(ref _lastUpdated, value); }
        }

        public DelegateCommand UpdateCommand { get; private set; }

        public ViewAViewModelBase(IEventAggregator eventAggregator)
        {

            _eventAggregator = eventAggregator;
            UpdateCommand =
            new DelegateCommand(UpdateMethod, CanUpdateMethod)
            .ObservesProperty(() => FirstName)
            .ObservesProperty(() => LastName);
        }

        protected bool CanUpdateMethod()
        {
            return !String.IsNullOrEmpty(_lastName) && !String.IsNullOrEmpty(_firstName);
        }

        protected virtual  void UpdateMethod()
        {

            LastUpdated = DateTime.Now;
            _eventAggregator.GetEvent<Events.UpdatedAggEvent>().Publish($"User {FirstName}");
        }
    }
}

它对我很有魅力。我想如果您需要来自其他程序集的另一个对象,您可以在基类中创建它们的实例祝您
好运

于 2016-07-08T18:09:18.213 回答
1

我终于通过设置自定义视图模型解析器在所有添加的程序集目录中搜索视图模型来解决我的问题。

解决方案

首先,我尝试应用默认的棱镜视图模型定位器约定,如果没有找到视图模型,我开始应用我的自定义。

1-我首先从 AggregateCatalog 获取所有程序集。

2-我获取所有从 Prism BindableBase 继承的非抽象导出类型。

3-我应用自定义约定委托来获得预期的视图模型。

在我的例子中,自定义约定是所有具有后缀“ViewModel”的类型,前缀是视图类型名称: 示例:如果视图名称是“UsersView”,则视图模型应该是“UsersViewModel”。如果视图名称是“Users”,则视图模型也应该是“UsersViewModel”。

代码:

ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType =>
                {
                    // The default prism view model type resolver as Priority 
                    Type viewModelType = this.GetDefaultViewModelTypeFromViewType(viewType);
                    if (viewModelType != null)
                    {
                        return viewModelType;
                    }

                    // IF no view model found by the default prism view model resolver

                    // Get assembly catalogs
                    var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

                    // Get all exported types inherit from BindableBase prism class
                    var bindableBases =
                        assemblyCatalogs.Select(
                            c =>
                            ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                                .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
                                .Select(t => t)).SelectMany(b =>
                                    {
                                        var types = b as IList<Type> ?? b.ToList();
                                        return types;
                                    }).Distinct() ;

                    // Get the type where the delegate is applied
                    var customConvention = new Func<Type, bool>(
                        (Type t) =>
                            {
                                const string ViewModelSuffix = "ViewModel";
                                var isTypeWithViewModelSuffix = t.Name.EndsWith(ViewModelSuffix);
                                return (isTypeWithViewModelSuffix)
                                       && ((viewType.Name.EndsWith("View") && viewType.Name + "Model" == t.Name)
                                           || (viewType.Name + "ViewModel" == t.Name));
                            });

                    var resolvedViewModelType = bindableBases.FirstOrDefault(customConvention);
                    return resolvedViewModelType;
                });

该方法 * GetDefaultViewModelTypeFromViewType * 是默认的棱镜视图模型定位器,其代码与Bart 的答案完全相同。我希望这对其他人有帮助。

编辑:

我终于通过创建一个新的自定义 MvvmTypeLocator 解决了这个问题:

public interface IMvvmTypeLocator
{
    #region Public Methods and Operators

    Type GetViewModelTypeFromViewType(Type viewType);

    Type GetViewTypeFromViewModelType(Type viewModelType);

    Type GetViewTypeFromViewName(string viewName);

    #endregion
}

实施:

public class MvvmTypeLocator: IMvvmTypeLocator
{
    private AggregateCatalog AggregateCatalog { get; set; }

    public MvvmTypeLocator(AggregateCatalog aggregateCatalog)
    {
        this.AggregateCatalog = aggregateCatalog;
    }

    public Type GetViewModelTypeFromViewType(Type sourceType)
    {
        // The default prism view model type resolver as Priority 
        Type targetType = this.GetDefaultViewModelTypeFromViewType(sourceType);
        if (targetType != null)
        {
            return targetType;
        }

        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(BindableBase)))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
            {
                const string TargetTypeSuffix = "ViewModel";
                var isTypeWithTargetTypeSuffix = t.Name.EndsWith(TargetTypeSuffix);
                return (isTypeWithTargetTypeSuffix)
                       && ((sourceType.Name.EndsWith("View") && sourceType.Name + "Model" == t.Name)
                           || (sourceType.Name + "ViewModel" == t.Name));
            });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    public Type GetViewTypeFromViewModelType(Type sourceType)
    { 
        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && t.IsSubclassOf(typeof(IView)))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
            {
                const string SourceTypeSuffix = "ViewModel";
                var isTypeWithSourceTypeSuffix = t.Name.EndsWith(SourceTypeSuffix);
                return (isTypeWithSourceTypeSuffix)
                       && ((sourceType.Name.EndsWith("View") && t.Name + "Model" == sourceType.Name)
                           || (t.Name + "ViewModel" == sourceType.Name));
            });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    public Type GetViewTypeFromViewName(string viewName)
    {
        // Get assembly catalogs
        var assemblyCatalogs = this.AggregateCatalog.Catalogs.Where(c => c is AssemblyCatalog);

        // Get all exported types inherit from BindableBase prism class
        var bindableBases =
            assemblyCatalogs.Select(
                c =>
                ((AssemblyCatalog)c).Assembly.GetExportedTypes()
                    .Where(t => !t.IsAbstract && typeof(IView).IsAssignableFrom(t) && t.Name.StartsWith(viewName))
                    .Select(t => t)).SelectMany(b =>
                    {
                        var types = b as IList<Type> ?? b.ToList();
                        return types;
                    }).Distinct();

        // Get the type where the delegate is applied
        var customConvention = new Func<Type, bool>(
            (Type t) =>
                {
                    return t.Name.EndsWith("View");
                });

        var resolvedTargetType = bindableBases.FirstOrDefault(customConvention);
        return resolvedTargetType;
    }

    private Type GetDefaultViewModelTypeFromViewType(Type viewType)
    {
        var viewName = viewType.FullName;
        viewName = viewName.Replace(".Views.", ".ViewModels.");
        var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
        var suffix = viewName.EndsWith("View") ? "Model" : "ViewModel";
        var viewModelName = String.Format(
            CultureInfo.InvariantCulture,
            "{0}{1}, {2}",
            viewName,
            suffix,
            viewAssemblyName);
        return Type.GetType(viewModelName);
    }
}

此自定义类型定位器使用 AggregateCatalog 在所有程序集目录中搜索目标类型。当然,一旦配置了 Bootstrapper 的 AggregateCatalog,我就会在 Bootstrapper 上创建它的实例:

protected override void ConfigureAggregateCatalog()
    {
        base.ConfigureAggregateCatalog();
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginViewModel).Assembly));
        AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(LoginView).Assembly));
        this.mvvmTypeLocator = new MvvmTypeLocator(this.AggregateCatalog);
    }

最后,我只需在 Bootstrapper 中配置视图模型定位器,如下所示:

protected override void ConfigureViewModelLocator()
    {
        base.ConfigureViewModelLocator();

        ViewModelLocationProvider.SetDefaultViewTypeToViewModelTypeResolver(
            viewType => this.mvvmTypeLocator.GetViewModelTypeFromViewType(viewType));
    }

请注意,方法GetViewTypeFromViewModelTypeGetViewTypeFromViewName正在搜索所有实现名为IView的接口的视图。这只是一个空接口,我用来将我的视图与同一程序集中的其他类区分开来。如果有人使用这个 mvvmTypeLocator,那么他必须创建自己的接口并实现 mvvmTypeLocator 应该可以发现的所有视图。

于 2015-12-31T20:50:14.627 回答