使用嵌套视图时 Prism 7 抛出异常

Prism 7 throws and exception when working with nested views

几个月前我发布了类似的问题,我仍然不确定正确的方法是什么。

假设你有一个视图A,在这个视图A中你声明了一个区域A,然后你在这个区域A中注入了一个视图B。同样,在视图B中你注册了区域B,然后你注入了一个将C看成B区,如下图所示:

在 ViewA 的 ViewModelA 中,我有一个方法 SetUpSubViews(),我在其中调用:

_regionManager.RequestNavigate("regionA", "ViewB", NavigationCallback);

View B 的 ViewModelB 实现了 INavigationAware。所以在 OnNavigatedTo() 方法中我调用:

_regionManager.RequestNavigate("regionB", "ViewC", NavigationCallback);

View C 的 ViewModelC 也实现了 INavigationAware。

现在,我在 IsNavigationTarget() 方法的 ViewModelB 和 ViewModelC 中都有:

public bool IsNavigationTarget(NavigationContext navigationContext)
    {            
        return false;
    }

这意味着我想在每次浏览此视图时创建新视图。

ViewB和ViewC都实现了IRegionMemberLifetime接口,我这里设置:

#region IRegionMemberLifetime

public bool KeepAlive => false;

#endregion

这意味着我不想重用视图,我希望它被处理掉。

视图中的区域声明如下:

<ContentControl prism:RegionManager.RegionName="{x:Static localRegions:LocalRegions.RegionB}" />

现在,我第一次在 ViewModelA 上调用 SetUpSubViews() 方法时一切正常。第二次调用它时,我看到异常:

Region with the given name is already registered ...

我需要的是有一种方法可以在每次需要时从头开始重新创建视图<->视图模型对。似乎在处理视图时,棱镜不会删除在已删除视图中声明的区域。向社区和棱镜开发人员提问,如何以正确的方式做到这一点?

目前的解决方案并不令人满意,这是我的做法: 第 1 步 - 我在 INavigationAware 部分的 ViewModelB 和 ViewModelC 中设置

    public bool IsNavigationTarget(NavigationContext navigationContext)
    {            
        return true;
    }

向 prism 发出不要创建新视图的信号,这可能还意味着如果在视图中找到任何区域不要在区域管理器中注册它。

第 2 步 - 当我需要将视图注入区域时,我手动删除旧视图并创建新视图。所以我的 SetUpSubViews() 方法看起来像这样:

protected void SetUpSubViews(){
            //get region by name
            var region = _regionManager.Regions["regionA"];
            // push to remove all views from the region
            region.RemoveAll();
            // navigate to view
           _regionManager.RequestNavigate("regionA", "ViewB", NavigationCallback);}

同样,我必须从 ViewB 上的区域 regionB 中删除 ViewC。 (这里region.RemoveAll()是重点线。)

Step3 - 我没有在 viewB 和 viewC 上实现 IRegionMemberLifetime 接口。

有效,但看起来不正确。

P.S。我也尝试过作用域管理器,但我不知道如何将新创建的作用域管理器传播到视图模型,因为它们是自动创建的,如果我通过构造函数解析它,我会得到主全局管理器而不是作用域。

谢谢。

这是个比较麻烦的问题。我推荐来自 Brian Lagunas 本人的视频,他在视频中提供了解决方案和解释。比如这个。 https://app.pluralsight.com/library/courses/prism-problems-solutions/table-of-contents

如果你能看的话。如果不是我会尽力解释。

我认为问题是容器中的 IRegionManager 是一个单例,无论何时使用它都是同一个实例,因此当您尝试在已经注入的区域中注入一个区域时,它不会工作,你需要有一个单独的 RegionManager 用于嵌套视图。

这应该可以解决问题。 创建两个接口

public interface ICreateRegionManagerScope
{
    bool CreateRegionManagerScope { get; }
}
public interface IRegionManagerAware
{
    IRegionManager RegionManager { get; set; }
}

创建 RegionManagerAwareBehaviour

public class RegionManagerAwareBehaviour : RegionBehavior
{
    public const string BehaviorKey = "RegionManagerAwareBehavior";

    protected override void OnAttach()
    {
        Region.Views.CollectionChanged += Views_CollectionChanged;
    }

    void Views_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (var item in e.NewItems)
            {
                IRegionManager regionManager = Region.RegionManager;

                // If the view was created with a scoped region manager, the behavior uses that region manager instead.
                if (item is FrameworkElement element)
                {
                    if (element.GetValue(RegionManager.RegionManagerProperty) is IRegionManager scopedRegionManager)
                    {
                        regionManager = scopedRegionManager;
                    }
                }

                InvokeOnRegionManagerAwareElement(item, x => x.RegionManager = regionManager);
            }
        }
        else if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach (var item in e.OldItems)
            {
                InvokeOnRegionManagerAwareElement(item, x => x.RegionManager = null);
            }
        }
    }

    private static void InvokeOnRegionManagerAwareElement(object item, Action<IRegionManagerAware> invocation)
    {
        if (item is IRegionManagerAware regionManagerAwareItem)
        {
            invocation(regionManagerAwareItem);
        }

        if (item is FrameworkElement frameworkElement)
        {
            if (frameworkElement.DataContext is IRegionManagerAware regionManagerAwareDataContext)
            {
                // If a view doesn't have a data context (view model) it will inherit the data context from the parent view.
                // The following check is done to avoid setting the RegionManager property in the view model of the parent view by mistake.
                if (frameworkElement.Parent is FrameworkElement frameworkElementParent)
                {
                    if (frameworkElementParent.DataContext is IRegionManagerAware regionManagerAwareDataContextParent)
                    {
                        if (regionManagerAwareDataContext == regionManagerAwareDataContextParent)
                        {
                            // If all of the previous conditions are true, it means that this view doesn't have a view model
                            // and is using the view model of its visual parent.
                            return;
                        }
                    }
                }

                invocation(regionManagerAwareDataContext);
            }
        }
    }
}

创建ScopedRegionNavigationContentLoader

public class ScopedRegionNavigationContentLoader : IRegionNavigationContentLoader
{
    private readonly IServiceLocator serviceLocator;

    /// <summary>
    /// Initializes a new instance of the <see cref="RegionNavigationContentLoader"/> class with a service locator.
    /// </summary>
    /// <param name="serviceLocator">The service locator.</param>
    public ScopedRegionNavigationContentLoader(IServiceLocator serviceLocator)
    {
        this.serviceLocator = serviceLocator;
    }

    /// <summary>
    /// Gets the view to which the navigation request represented by <paramref name="navigationContext"/> applies.
    /// </summary>
    /// <param name="region">The region.</param>
    /// <param name="navigationContext">The context representing the navigation request.</param>
    /// <returns>
    /// The view to be the target of the navigation request.
    /// </returns>
    /// <remarks>
    /// If none of the views in the region can be the target of the navigation request, a new view
    /// is created and added to the region.
    /// </remarks>
    /// <exception cref="ArgumentException">when a new view cannot be created for the navigation request.</exception>
    public object LoadContent(IRegion region, NavigationContext navigationContext)
    {
        if (region == null) throw new ArgumentNullException("region");
        if (navigationContext == null) throw new ArgumentNullException("navigationContext");

        string candidateTargetContract = this.GetContractFromNavigationContext(navigationContext);

        var candidates = this.GetCandidatesFromRegion(region, candidateTargetContract);

        var acceptingCandidates =
            candidates.Where(
                v =>
                {
                    var navigationAware = v as INavigationAware;
                    if (navigationAware != null && !navigationAware.IsNavigationTarget(navigationContext))
                    {
                        return false;
                    }

                    var frameworkElement = v as FrameworkElement;
                    if (frameworkElement == null)
                    {
                        return true;
                    }

                    navigationAware = frameworkElement.DataContext as INavigationAware;
                    return navigationAware == null || navigationAware.IsNavigationTarget(navigationContext);
                });


        var view = acceptingCandidates.FirstOrDefault();

        if (view != null)
        {
            return view;
        }

        view = this.CreateNewRegionItem(candidateTargetContract);

        region.Add(view, null, CreateRegionManagerScope(view));

        return view;
    }

    private bool CreateRegionManagerScope(object view)
    {
        bool createRegionManagerScope = false;

        if (view is ICreateRegionManagerScope viewHasScopedRegions)
            createRegionManagerScope = viewHasScopedRegions.CreateRegionManagerScope;

        return createRegionManagerScope;
    }

    /// <summary>
    /// Provides a new item for the region based on the supplied candidate target contract name.
    /// </summary>
    /// <param name="candidateTargetContract">The target contract to build.</param>
    /// <returns>An instance of an item to put into the <see cref="IRegion"/>.</returns>
    protected virtual object CreateNewRegionItem(string candidateTargetContract)
    {
        object newRegionItem;
        try
        {
            newRegionItem = this.serviceLocator.GetInstance<object>(candidateTargetContract);
        }
        catch (ActivationException e)
        {
            throw new InvalidOperationException(
                string.Format(CultureInfo.CurrentCulture, "Cannot create navigation target", candidateTargetContract),
                e);
        }
        return newRegionItem;
    }

    /// <summary>
    /// Returns the candidate TargetContract based on the <see cref="NavigationContext"/>.
    /// </summary>
    /// <param name="navigationContext">The navigation contract.</param>
    /// <returns>The candidate contract to seek within the <see cref="IRegion"/> and to use, if not found, when resolving from the container.</returns>
    protected virtual string GetContractFromNavigationContext(NavigationContext navigationContext)
    {
        if (navigationContext == null) throw new ArgumentNullException(nameof(navigationContext));

        var candidateTargetContract = UriParsingHelper.GetAbsolutePath(navigationContext.Uri);
        candidateTargetContract = candidateTargetContract.TrimStart('/');
        return candidateTargetContract;
    }

    /// <summary>
    /// Returns the set of candidates that may satisfiy this navigation request.
    /// </summary>
    /// <param name="region">The region containing items that may satisfy the navigation request.</param>
    /// <param name="candidateNavigationContract">The candidate navigation target as determined by <see cref="GetContractFromNavigationContext"/></param>
    /// <returns>An enumerable of candidate objects from the <see cref="IRegion"/></returns>
    protected virtual IEnumerable<object> GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
    {
        if (region == null) throw new ArgumentNullException(nameof(region));
        return region.Views.Where(v =>
            string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
            string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
    }
}

在你的App.xaml

protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
 containerRegistry.RegisterSingleton<IRegionNavigationContentLoader,ScopedRegionNavigationContentLoader>();
    
}

protected override void ConfigureDefaultRegionBehaviors(IRegionBehaviorFactory regionBehaviors)
    {
       base.ConfigureDefaultRegionBehaviors(regionBehaviors);

       regionBehaviors.AddIfMissing(RegionManagerAwareBehaviour.BehaviorKey, typeof(RegionManagerAwareBehaviour));
    }

即将结束。 现在在你的 ViewModelB 中实现 IRegionManagerAware 并将其作为正常的 属性

public IRegionManager RegionManager { get; set; }

然后在您的 ViewB 实施 ICreateRegionManagerScope 并将其作为 get 属性

public bool CreateRegionManagerScope => true;

现在应该可以了。

我再次强烈推荐 Pluralsight 上 Brian on Prism 的视频。当您开始使用 Prism 时,他有几个视频可以提供很多帮助。