如何解决使用嵌套视图时,Prism 7引发异常
几个月前,我Working with nested views using Prism with IsNavigationTarget which can return false发表了类似的问题,我仍然不确定什么是正确的方法。
假设您有一个视图A,在该视图A中您声明了一个区域A,然后将视图B注入了该区域A。类似地,在视图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()方法时,一切都很好。第二次当我调用它时,我看到了异常:
具有给定名称的区域已被注册 ...
我需要的是每次需要时都有一种从头开始重新创建view viewmodel对的方法。似乎在放置视图时,棱镜不会删除在删除的视图中声明的区域。向社区和棱镜开发者提出疑问,如何以正确的方式做到这一点?
当前的解决方案并不令人满意,这是我要做的: 第1步-我在INavigationAware部分的ViewModelB和ViewModelC中设置
public bool IsNavigationTarget(NavigationContext navigationContext)
{
return true;
}
通知棱镜不创建新视图,这也可能意味着如果在视图中发现任何区域,也不要在区域管理器中注册该区域。
第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",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,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;
现在应该可以了。
再次,我真的推荐Brian在Prism上在Pluralsight上播放的视频。当您开始使用Prism时,他有一些视频对您有很大帮助。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。