3
votes

I have written an MVVM app in WPF using MEF and Prism with three different regions. Code is across two modules, being discovered in the App.Config.

I have all the navigation commands and structure working perfectly fine, but the one thing I am confused on is how to set the initial Views loaded into each region at app startup as there seems to be nowhere I can do this. Furthermore if I add something to the end of the MainViewModel constructor to explicitly navigate to screen set A, something else seems to be overriding it and loading a different set of views to start with.

It also seems dependent on what order I load the modules in on the app.config which seems undesirable. If I load the Admin module last, it loads a set of screens from the admin module, if I load the search module last, it loads a set of views from the search module and in this case, it is not even finding a View for the main region.

What is the method for specifying what Views are loaded into each region at app startup when using MEF and config discovery?

using System;
using System.ComponentModel.Composition;
using Microsoft.Practices.Prism.Regions;

namespace CRM.GUI.WPF.Shared.Infrastructure.Behaviour
{
    [Export(typeof(AutoPopulateExportedViewsBehavior))]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class AutoPopulateExportedViewsBehavior : RegionBehavior, IPartImportsSatisfiedNotification
    {
        protected override void OnAttach()
        {
            AddRegisteredViews();
        }

        public void OnImportsSatisfied()
        {
            AddRegisteredViews();
        }

        private void AddRegisteredViews()
        {
            if (Region != null)
            {
                foreach (var viewEntry in RegisteredViews)
                {
                    if (viewEntry.Metadata.RegionName == Region.Name)
                    {
                        var view = viewEntry.Value;

                        if (!Region.Views.Contains(view))
                        {
                            Region.Add(view);
                        }
                    }
                }
            }
        }

        [ImportMany(AllowRecomposition = true)]
        public Lazy<object, IViewRegionRegistration>[] RegisteredViews { get; set; }
    }
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
    [MetadataAttribute]
    public class ViewExportAttribute : ExportAttribute, IViewRegionRegistration
    {
        public ViewExportAttribute()
            : base(typeof(object))
        { }

        public ViewExportAttribute(string viewName)
            : base(viewName, typeof(object))
        { }

        public string RegionName { get; set; }
    }

Used

[ViewExport(RegionName = RegionNames.MainRegion)]
public partial class ReportView
2
Hi! It's really hard to tell. Do you have a region behavior which imports the views? Can you post it?Marc
Updated to include codeNZJames
Looks good, if you set abreakpoint in the OnAttach() method: Is it hit, are what does RegisteredViews contain? Can you post the shell, too?Marc

2 Answers

1
votes

Based on my understanding, Prism loads and show by default the first View that gets registered on each Region (Only the first View would be shown if the region is set on a ContentControl item).

Therefore, you could deactivate the undesired Views on each RegionBehavior you don't want to show at startUp. This would make that when the desired StartUp View is added, it would get activated as there is no other active View yet.

Another alternative would be to register each View on the corresponding Module initialize() method, instead of using RegionBehaviours. So finally, after adding each View to the corresponding Region, you would decide to deactivate the View whether it is the StartUp View or not.

UPDATE:

The following implementation shows a possible alternative for deactivating non-startup Views on each RegionBehavior. In order to get a more elegant solution, you could create a dictionary or a simple static class that would return the StartUpView name for the corresponding Region, and then call it as shown below:

private void AddRegisteredViews()
{
   ...
       var view = viewEntry.Value;

       if (!Region.Views.Contains(view))
       {
            Region.Add(view);
            if (view.GetType().Name != StartUpViewNames.getViewNameFromRegion(Region))
            {
                 Region.deactivate(view);
            }
       }
  ...
}

Notice that I after the StartUpView is found and it is kept active, it continues deactivating the following added views, but you could leave them active. As I mentioned, the View that would be shown, would be the first one which gets Active in the Region.

I hope this helps, Regards.

0
votes

I believe the PRISM view philosophy is to load all the views and switch them in and out like a stack of cards. In your case, I can see you are using an attached behavior to load all of your views for each region. I suspect that all of your views are being loaded and added to the appropriate region, but that the last view is over the others (having been added last to the region). In WPF, the last control in the view hierarchy is higher in the zorder (because it is the last to be painted). Each of the previous controls is likely underneath.

Instead of your current approach, I suggest you create a service class called NavigationService, that will handle loading a view set from a module on demand, and unloading any previous view set. You can design it to be a state manager, and it can coordinate different view arrangements with PRISM, even if you only want certain views loaded (say a sub-set of a module's views), instead of loading all of the views all the time. In your Bootloader, register each module with the NavigationService, and perhaps have each module include a strategy class for each view combination you need, supplying appropriate behavior. This way you keep the coordination logic close to the views it uses. In your Shell, call the NavigationService (which will be injected into each of your view models to facilitate changing screens) to set the initial view.

Remember, this is just an idea to accomplish what you need, but I would get away from the basic PRISM functionality in WPF. It is limiting, imho, and unnecessary in complexity. You can still use PRISM's regions without using its navigation facility.