WPF MEF + Prism initial Region loading
Asked Answered
L

2

3

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
Lax answered 22/10, 2013 at 19:10 Comment(3)
Hi! It's really hard to tell. Do you have a region behavior which imports the views? Can you post it?Brookner
Updated to include codeLax
Looks good, if you set abreakpoint in the OnAttach() method: Is it hit, are what does RegisteredViews contain? Can you post the shell, too?Brookner
P
1

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.

Patrizius answered 23/10, 2013 at 17:27 Comment(4)
Thanks for the response. Can you just clarify, how would I deactivate the view on the region behaviour? I got around this problem by removing the custom attribute I added for automatic discovery for the unwanted views and so I only get the one with the [ViewExport] attribute on it displayed at startup, however when I navigate, it still finds the other views. How are they available to be found if I haven't given them a [ViewExport] attribute and added them to that region? Are they being added elsewhere?Lax
To confirm, I only have an [Export] attribute on the other view, no mention of which region I want it added to, so I can't see anywhere where I am specifying which region to put it in, only when I do the regionmanager.RequestNavigate("regionname", "viewname") is the only place I can see where this view is tied to the region, but yet it still works and displays correctly which confuses me, as if I don't need the [ViewExport(RegIonName="RegionName"] attribute, why am I even doing it? Without that attribute, the auto discovery code I pasted surely isn't picking up this view?Lax
The [Export] attribute makes the View be registered into the MEF container. When you navigate to the target View for the first time, this is not yet registered into the Region. However, RequestNavigate() method looks for the View into the MEF container if it was not found on the Region's Views collection. Therefore, as the View was registered in the container, the View is added to the Region and the Navigation is performed afterwards.Patrizius
You may find more related information on the Managing Dependencies Prism MSDN chapter, or at the Prism library source code which is available for download here. I hope this helped you.Patrizius
L
0

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.

Livingstone answered 23/10, 2013 at 16:55 Comment(1)
Thanks for the response, it's a good comment and I'll look into something of this kind to replace the Prism behaviour as I don't like having to declare identical duplicated navigation controller helper code in each moduleLax

© 2022 - 2024 — McMap. All rights reserved.