c# - 使用嵌套视图时 Prism 7 抛出和异常
问题描述
几个月前我发布了类似的问题使用带有 IsNavigationTarget 的 Prism 的嵌套视图可以返回 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() 方法时,一切都很好。第二次调用它时,我看到了异常:
具有给定名称的区域已注册...
我需要的是有一种方法可以在每次需要时从头开始重新创建视图<->视图模型对。似乎在处理视图时,棱镜不会删除在已删除视图中声明的区域。向社区和棱镜开发人员提问,如何以正确的方式做到这一点?
当前的解决方案并不令人满意,这就是我要做的:第 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 接口。
它有效,但看起来不正确。
PS我也尝试过作用域管理器,但我不知道如何将新创建的作用域管理器传播到视图模型,因为它们是自动创建的,如果我通过构造函数解决它,我会得到主全局管理器而不是作用域。
谢谢。
解决方案
这是一个相当麻烦的问题。我推荐 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
上并将其作为获取属性
public bool CreateRegionManagerScope => true;
现在它应该可以工作了。
我再次真正推荐来自 Brian on Prism 的 Pluralsight 视频。当您开始使用 Prism 时,他有几个视频对您有很大帮助。
推荐阅读
- amazon-quicksight - Quicksight:使用计算的 LAA 字段排名来计算另一个计算的维度字段
- python-3.x - 从 cv2.selectROIs 函数对多个 ROI 应用背景图像减法
- python - Sklearn 朴素贝叶斯打印概率结果
- macos - 谷歌表格卡在“工作”上,公式工作异常
- vba - 在创建用户表单时,我遇到了一些错误
- javascript - 使用 Popper Js 创建 Vue 组件下拉菜单
- mailchimp - 如何在 Mailchimp HTML 模板中迭代附件?
- python - 为什么 windows powershell 上的“pipenv install requests”命令显示错误?
- c# - C# 授权 - 如何在方法的逻辑中使用声明
- python - 最快的方法找到两个列表的共同元素而不改变第一个列表的顺序