Prism 7 throws and exception when working with nested views

后端 未结 1 768
耶瑟儿~
耶瑟儿~ 2021-01-25 06:08

I posted similar question few months ago Working with nested views using Prism with IsNavigationTarget which can return false, I\'m still not sure what is the proper way to do i

相关标签:
1条回答
  • 2021-01-25 06:52

    This is quite a troublesome problem. I recommend videos from Brian Lagunas himself where he provides a solution and explanation. For example this one. https://app.pluralsight.com/library/courses/prism-problems-solutions/table-of-contents

    If you can watch it. If not I will try to explain.

    The problem I believe is that IRegionManager from the container is a singleton and whenever you use it it is the same instance, so when you are trying to inject a region in an already injected region it will not work and you need to have a separate RegionManager for nested views.

    This should fix it. Create two interfaces

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

    Create a 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);
                }
            }
        }
    }
    

    Create 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));
        }
    }
    

    In your 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));
        }
    

    Coming to the finish. Now in your ViewModelB implement IRegionManagerAware and have it as a normal property

    public IRegionManager RegionManager { get; set; }
    

    Then at your ViewB implement ICreateRegionManagerScope and have it as a get property

    public bool CreateRegionManagerScope => true;
    

    Now it should work.

    Again I truly recommend the videos at Pluralsight from Brian on Prism. He has a couple of videos that help a lot when you are starting with a Prism.

    0 讨论(0)
提交回复
热议问题