Броски и исключение Prism 7 при работе с вложенными представлениями
Я опубликовал аналогичный вопрос несколько месяцев назад. Работая с вложенными представлениями с использованием Prism с IsNavigationTarget, который может возвращать false, я до сих пор не уверен, как это сделать правильно.
Представьте, что у вас есть представление A, в этом представлении A вы объявили область A, затем вы внедрили представление B в эту область A. Аналогично, в представлении B вы зарегистрировали область B, а затем вы внедрили представление C в эту область B. Как показано на следующем рисунке:
В ViewModelA для ViewA у меня есть метод SetUpSubViews(), в котором я вызываю:
_regionManager.RequestNavigate("regionA", "ViewB", NavigationCallback);
ViewModelB для View B реализует INavigationAware. Итак, в методе OnNavigatedTo() я вызываю:
_regionManager.RequestNavigate("regionB", "ViewC", NavigationCallback);
ViewModelC для View C также реализует INavigationAware.
Теперь у меня есть как ViewModelB, так и ViewModelC в методе IsNavigationTarget():
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}" />
Теперь, когда я впервые вызываю метод SetUpSubViews() в ViewModelA, все в порядке. Когда я звоню во второй раз, я вижу исключение:
Регион с данным названием уже зарегистрирован...
Что мне нужно, так это иметь способ воссоздавать пару view <-> viewmodel с нуля каждый раз, когда мне это нужно. Кажется, что когда вид удален, призма не удаляет область, которая была объявлена в удаленном виде. Вопрос к сообществу и разработчикам призмы, как это сделать правильно?
Текущее решение не радует, вот что я делаю: Шаг 1 - Я устанавливаю ViewModelB и ViewModelC в части INavigationAware
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", "ViewB", NavigationCallback);}
Точно так же мне нужно удалить ViewC из региона region B на ViewB. (здесь region.RemoveAll() - ключевая строка.)
Шаг 3 - Я не реализую интерфейс IRegionMemberLifetime на view B и viewC.
Это работает, но выглядит неправильно.
PS Я также попробовал диспетчер с областью действия, но я не знаю, как распространить новый созданный диспетчер с областью действия на модели просмотра, потому что они создаются автоматически, и если я разрешаю его с помощью конструктора, я получаю главный глобальный менеджер вместо области действия.
Спасибо.
0 ответов
Это довольно хлопотная проблема. Я рекомендую видео от самого Брайана Лагунаса, где он предлагает решение и объяснение. Например этот.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;
Теперь должно работать.
Опять же, я искренне рекомендую видео от Брайана на Prism на Pluralsight. У него есть пара видеороликов, которые очень помогают, когда вы начинаете с Prism.