publicclassMainWindowViewModel : BindableBase
{
privatereadonlyIRegionManager_regionManager;
publicDelegateCommand<string> NavigateCommand { get; }
publicMainWindowViewModel(IRegionManagerregionManager)
{
_regionManager = regionManager;
NavigateCommand = newDelegateCommand<string>(Navigate);
}
// ItemsListView is a command parameter passed as navigationPathvoidNavigate(stringnavigationPath)
{
// open/reuse a tab and inject a view that has the same name as the navigationPath_regionManager.RequestNavigate("TabRegion", navigationPath);
}
}
Navigation configuration
App.xaml.cs
protectedoverridevoidRegisterTypes(IContainerRegistrycontainerRegistry)
{
// register the views to allow RequestNavigate with the view name
containerRegistry.RegisterForNavigation<ItemsListView>();
containerRegistry.RegisterForNavigation<ItemView>();
}
<Window.Resources><StyleTargetType="TabItem"><!-- redefine the header template with a button --><SetterProperty="HeaderTemplate"><Setter.Value><DataTemplate><DockPanel><ButtonDockPanel.Dock="Right"Style="{StaticResource TabCloseButton}"><b:Interaction.Triggers><b:EventTriggerEventName="Click"><local:CloseTabAction /></b:EventTrigger></b:Interaction.Triggers></Button><ContentControlContent="{Binding}"VerticalAlignment="Center" /></DockPanel></DataTemplate></Setter.Value></Setter></Style></Window.Resources>
publicsealedclassCloseTabAction : TriggerAction<Button>
{
protectedoverride void Invoke(object parameter)
{
var args = parameter as RoutedEventArgs;
if (args == null)
return;
var tabItem = VisualTreeHelperPlus.GetParent<TabItem>(args.OriginalSource as DependencyObject);
if (tabItem == null)
return;
var tabControl = VisualTreeHelperPlus.GetParent<TabControl>(tabItem);
if (tabControl == null)
return;
var region = RegionManager.GetObservableRegion(tabControl).Value;
if (region == null)
return;
if (region.Views.Contains(tabItem.Content))
region.Remove(tabItem.Content); // remove the view from the region// Prism will remove automatically the containing element (TabItem) from the hosting control (TabControl)
}
}
<!-- add a TabControl with a region named ChildRegion in the ItemView --><TabControlGrid.Row="1"mvvm:RegionManager.RegionName="ChildRegion" />
Problem is the region names have to be unique. So if I implement 2 instances of ItemView I will have 2 regions with the name ChildRegion.
Solution to this is to use scoped regions: 1 RegionManager per ItemView.
ScopedRegionNavigationContentLoader
ScopedRegionNavigationContentLoader.cs
publicsealedclassScopedRegionNavigationContentLoader : RegionNavigationContentLoader
{
publicScopedRegionNavigationContentLoader(IContainerExtensioncontainer) : base(container)
{ }
protectedoverridevoidAddViewToRegion(IRegionregion, objectview)
{
// create a scoped region if the view or the vm has the property CreateRegionManagerScope == true
region.Add(view, null, CreateRegionManagerScope(view));
}
privateboolCreateRegionManagerScope(objectview)
{
if (view is ICreateRegionManagerScope viewHasScopedRegions)
{
return viewHasScopedRegions.CreateRegionManagerScope;
}
if (view is FrameworkElement frameworkElement &&
frameworkElement.DataContext is ICreateRegionManagerScope viewModelHasScopedRegions)
{
return viewModelHasScopedRegions.CreateRegionManagerScope;
}
returnfalse;
}
}
App.xaml.cs
protectedoverridevoidRegisterTypes(IContainerRegistrycontainerRegistry)
{
// register the ScopedRegionNavigationContentLoader to be the IRegionNavigationContentLoader used by this App
containerRegistry.RegisterSingleton<IRegionNavigationContentLoader, ScopedRegionNavigationContentLoader>();
}
ItemViewModel.cs
publicsealedclassItemViewModel : BindableBase, INavigationAware, ICreateRegionManagerScope
{
// ItemViewModel implements ICreateRegionManagerScope and set CreateRegionManagerScope to true// so the regions inside the ItemView will be scoped regionspublicbool CreateRegionManagerScope => true;
}
RegionManagerAware
Problem is now we doesn't want to access to the main RegionManager but to the scoped RegionManager (the one of the ItemView) to inject a view inside.
RegionManagerAwareBehavior.cs
publicclassRegionManagerAwareBehavior : RegionBehavior
{
publicconststringBehaviorKey = nameof(RegionManagerAwareBehavior);
protectedoverridevoidOnAttach()
{
Region.Views.CollectionChanged += OnCollectionChanged;
}
privatevoidOnCollectionChanged(objectsender, NotifyCollectionChangedEventArgse)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (var newItem in e.NewItems)
{
varregionManager = Region.RegionManager;
// if the view was created with a scoped region manager, the behavior uses that region manager instead.if (newItem is FrameworkElement frameworkElement
&& frameworkElement.GetValue(RegionManager.RegionManagerProperty) isIRegionManagerscopedRegionManager)
{
regionManager = scopedRegionManager;
}
InvokeOnRegionManagerAwareElement(newItem, x => x.RegionManager = regionManager);
}
}
elseif (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (var oldItem in e.OldItems)
{
InvokeOnRegionManagerAwareElement(oldItem, x => x.RegionManager = null);
}
}
}
privatestaticvoidInvokeOnRegionManagerAwareElement(objectitem, Action<IRegionManagerAware> invocation)
{
if (item is IRegionManagerAware regionManagerAwareItem)
{
invocation(regionManagerAwareItem);
}
if (item is FrameworkElement frameworkElement
&& frameworkElement.DataContext is IRegionManagerAware regionManagerAwareDataContext)
{
// if a view doesn't have a DataContext (VM) it will inherit from the DataContext of the parent view.// the following check is done to avoid setting the RegionManager property in the VM of the parent view by mistake.if (frameworkElement.Parent is FrameworkElement frameworkElementParent
&& frameworkElementParent.DataContext is IRegionManagerAware regionManagerAwareDataContextParent
&& 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 VM of its visual parent.return;
}
invocation(regionManagerAwareDataContext);
}
}
}