Prism loading dependent views

De Banane Atomic
Aller à la navigationAller à la recherche

Description

When a view is injected into a region, trigger the load of other views in other regions and pass them the same DataContext.

Region behavior and Attribute

Add a region behavior which create the dependent views and inject them into the target region.

DependentViewAttribute.cs
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependentViewAttribute : Attribute
{
    public Type Type { get; }
    public string TargetRegionName { get; }

    public DependentViewAttribute(Type viewType, string targetRegionName)
    {
        Type = viewType;
        TargetRegionName = targetRegionName;
    }
}
DependentViewRegionBehavior.cs
public class DependentViewRegionBehavior : RegionBehavior
{
    private readonly Dictionary<object, List<DependentViewInfo>> _dependentViewCache = new Dictionary<object, List<DependentViewInfo>>();

    protected override void OnAttach()
    {
        Region.ActiveViews.CollectionChanged += ActiveViewsOnCollectionChanged;
    }

    private void ActiveViewsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (var view in e.NewItems)
            {
                if (!_dependentViewCache.TryGetValue(view, out var viewInfoList))
                {
                    viewInfoList = new List<DependentViewInfo>();
                    foreach (var attribute in GetCustomAttribute<DependentViewAttribute>(view.GetType()))
                    {
                        var viewInfo = CreateDependentView(attribute);
                        if (viewInfo.View is FrameworkElement viewToInject &&
                            view is FrameworkElement viewTrigger)
                        {
                            viewToInject.DataContext = viewTrigger.DataContext;
                        }

                        viewInfoList.Add(viewInfo);
                    }

                    if (!_dependentViewCache.ContainsKey(view))
                    {
                        _dependentViewCache.Add(view, viewInfoList);
                    }
                }

                viewInfoList.ForEach(x => Region.RegionManager.Regions[x.TargetRegionName].Add(x.View));
            }
        }

        if (e.Action == NotifyCollectionChangedAction.Remove)
        {
            foreach (var view in e.OldItems)
            {
                if (_dependentViewCache.TryGetValue(view, out var viewInfoList))
                {
                    viewInfoList.ForEach(x => Region.RegionManager.Regions[x.TargetRegionName].Remove(x.View));
                }
            }
        }
    }

    private DependentViewInfo CreateDependentView(DependentViewAttribute attribute)
    {
        return new DependentViewInfo
        {
            View = attribute.Type != null ? Activator.CreateInstance(attribute.Type) : null,
            TargetRegionName = attribute.TargetRegionName
        };
    }

    private static IEnumerable<T> GetCustomAttribute<T>(Type type) => type.GetCustomAttributes(typeof(T), true).OfType<T>();
}

internal class DependentViewInfo
{
    public object View { get; set; }
    public string TargetRegionName { get; set; }
}
App.xaml.cs
protected override void ConfigureDefaultRegionBehaviors(IRegionBehaviorFactory regionBehaviors)
{
    base.ConfigureDefaultRegionBehaviors(regionBehaviors);

    regionBehaviors.AddIfMissing(nameof(DependentViewRegionBehavior), typeof(DependentViewRegionBehavior));
}

Views and ViewModels

MainWindow.xaml
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Menu prism:RegionManager.RegionName="MenuRegion" />

    <ContentControl Grid.Row="1"
                    prism:RegionManager.RegionName="StepRegion" />
</Grid>
StepOneView.xaml.cs
[DependentView(typeof(NextMenuItemView), "MenuRegion")]
public partial class StepOneView
{ }
StepOneViewModel.cs
public class StepOneViewModel : BindableBase
{
    private readonly IRegionManager _regionManager;
    public ICommand GoToNextStepCommand { get; }

    public StepOneViewModel(IRegionManager regionManager)
    {
        _regionManager = regionManager;
        GoToNextStepCommand = new DelegateCommand(GoToNextStep);
    }

    private void GoToNextStep()
    {
        _regionManager.RequestNavigate("StepRegion", "StepTwoView");
    }
}
StepTwoView.xaml.cs
[DependentView(typeof(PreviousMenuItemView), "MenuRegion")]
[DependentView(typeof(NextMenuItemView), "MenuRegion")]
public partial class StepTwoView
{ }
StepTwoViewModel.cs
public class StepTwoViewModel : BindableBase
{
    private readonly IRegionManager _regionManager;
    public ICommand GoToPreviousStepCommand { get; }
    public ICommand GoToNextStepCommand { get; }

    public StepTwoViewModel(IRegionManager regionManager)
    {
        _regionManager = regionManager;
        GoToPreviousStepCommand = new DelegateCommand(GoToPreviousStep);
        GoToNextStepCommand = new DelegateCommand(GoToNextStep);
    }

    private void GoToPreviousStep()
    {
        _regionManager.RequestNavigate("StepRegion", "StepOneView");
    }

    private void GoToNextStep()
    {
        _regionManager.RequestNavigate("StepRegion", "StepThreeView");
    }
}
StepThreeView.xaml.cs
[DependentView(typeof(PreviousMenuItemView), "MenuRegion")]
public partial class StepThreeView
{ }
StepThreeViewModel.cs
public class StepThreeViewModel : BindableBase
{
    private readonly IRegionManager _regionManager;
    public ICommand GoToPreviousStepCommand { get; }

    public StepThreeViewModel(IRegionManager regionManager)
    {
        _regionManager = regionManager;
        GoToPreviousStepCommand = new DelegateCommand(GoToPreviousStep);
    }

    private void GoToPreviousStep()
    {
        _regionManager.RequestNavigate("StepRegion", "StepTwoView");
    }
}