Load View from Ondemand Module from shell

Topics: Prism v4 - WPF 4
May 7, 2011 at 9:44 AM

Hi All,

This is Mahesh. Currently I'm working with PRISM,MEF and WPF. I need a help in injecting the view from the module which is loading ondemand into the Shell.

I have created a Shell with 2 buttons. When I click the button it should load the view from the module. This module is OnDemand. So how do I implement this.

Here is the code, which I tried.

Shell

<Window x:Class="Prism101.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://www.codeplex.com/prism"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ItemsControl Name="itmControl">
            <Button Name="btnLoad" Content="Load Module" Width="100" Height="50" Click="btnLoad_Click"/>
        </ItemsControl>

        <ContentControl prism:RegionManager.RegionName="MainContent"/>
    </Grid>
</Window>

Shell Codebehind

 public partial class MainWindow : Window, IPartImportsSatisfiedNotification
    {
        [Import]
        private IRegionManager regionManager;

        [Import]
        private IModuleManager moduleManager;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void btnLoad_Click(object sender, RoutedEventArgs e)
        {
            var mainRegion = regionManager.Regions["MainContent"];
           

           
        }

        #region IPartImportsSatisfiedNotification Members

        public void OnImportsSatisfied()
        {
          
        }

        #endregion
    }

My Module class

[ModuleExport(typeof(Module))]
    public class Module : IModule
    {
        IRegionManager _RegionManager;
        [ImportingConstructor]
        public Module(IRegionManager regionManager)
        {
            _RegionManager = regionManager;
        }
        public void Initialize()
        {
            _RegionManager.RegisterViewWithRegion("MainContent",typeof(UserControl1));
        }
    }

I don't want to mention the RegionName - "MainContent" in the module. Is there anyway to call the view from the module(OnDemand) and load in the shell.

May 9, 2011 at 9:01 PM

Hi Mahesh,

Based on my understanding of your scenario, you could load on demand your module in your Shell´s btnLoad click event by doing this modification in your code:

[Export]
public partial class MainWindow : Window, IPartImportsSatisfiedNotification
{
    private readonly IRegionManager _regionManager;
    private readonly IModuleManager _moduleManager;
 
    [ImportingConstructor]       
    public MainWindow(IRegionManager regionManager, IModuleManager moduleManager)
    {
       InitializeComponent();
       this._regionManager= regionManager;
       this._moduleManager = moduleManager;
    }
 
    private void btnLoad_Click(object sender, RoutedEventArgs e)
    {
        this._moduleManager.LoadModule(“Module”);           
    }
    …   
}

This code will load your module class on demand, and since your Initialize method is registering the view UserControl1 with MainContent region, it will load your view into aforementioned region in your shell.

You should take into account that UserControl1 must be decorated with the Export Attribute.

On the other hand, another approach could be to load your module on demand in the btnLoad_Click event and in the OnImportSatisfied handler, subscribe to ModuleManager´s LoadModuleCompleted. In this handler you could use the ServiceLocator to retrieve your UserControl1 view instance and add it to MainContent region like this:

public void OnImportsSatisfied()
{          
    …        
    this.moduleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted;
}

private void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
    var myView = ServiceLocator.Current.GetInstance<IUserControl1>();
    this._regionManager.Regions[“MainContent”].Add(myView);
}

It should be noted that the way UI Composition is performed in Prism is by declaring named regions (for example, in the shell) and adding views from other units of functionality (for example, modules) into that regions. In order to achieve that, the component adding views to a region must know that region’s name, or else there is no way for that component to indicate where to place that visual component. On the other hand, the shell should have no notion of the views that are being added to a region inside it from a separate module; that’s one way to decouple visual components from the layout in which they’re being added in Prism.

Please let me know if this information helps you.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

May 10, 2011 at 5:32 AM

Hi mbronzovic,

In the below code snippet how can I get the IUserControl1. Because I didn't add any reference of my Module in the Shell. So how can I get the IUserControl interface from MyModule.

private void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
    var myView = ServiceLocator.Current.GetInstance<IUserControl1>();
    this._regionManager.Regions[“MainContent”].Add(myView);
}



Thanks

Mahesh

May 10, 2011 at 6:55 AM

Hi Mahesh,

The whole point of Modularity is to keep your application as loosely coupled as possible. In your case you should probably have a shared dll which has the IUserControl1 interface type in it, rather than referencing the module you are downloading using ModuleManager.

May 11, 2011 at 5:18 AM

Hi gan_s,

Thanks a lot ! I created a common library with a Interface and loaded the view in the Shell dynamically.

Thanks very much for the support. Soon I will be back with another scenario.

Regards

Mahesh

 

 

May 13, 2011 at 11:04 AM

Hi,

private void LoadOnDemandModule(string ModuleName, string ViewName, string RegionName)
        {
           
            this._moduleManager.LoadModule(ModuleName);

            var myView = ServiceLocator.Current.GetInstance<IModule>(ViewName);
            var currentView = this._regionManager.Regions[RegionName].Views.Contains(myView);

            if (currentView)
            {
                this._regionManager.Regions[RegionName].Remove(myView);
            }

            this._regionManager.Regions[RegionName].Add(myView);
            this._regionManager.Regions[RegionName].Activate(myView);

           
        }

 

I used this method to load the module dynamically in SilverLight. I'm getting activation error "Activation error occured while trying to get instance of type IModule, key "MyView1"".

This code works in WPF. But in silverlight getting error.

May 13, 2011 at 11:13 AM

Hi Mahesh,

I think you should be doing the region.Add in the Module class in your Module. Something like this

 

[Module(ModuleName = "Module")]
public class Module : IModule
{
       private IRegionManager _regionManager;
        
       public Module(IRegionManager regionManager)
       {
             _regionManager = regionManager;
        }

 public void Initialize() 
        {
             var view = ServiceLocator.Current.GetInstance<MyView>(ViewName);
             _regionManager.Regions[RegionName].Add(view, ViewName);
        }
}

I think you are trying to get the instance of IModule with your ViewName. Try the above. It should work.

 

 

May 13, 2011 at 12:58 PM

Hi gan_s,

I don't want to mention the RegionName in the Module as you said. I just want to call the view in the Shell and load in the region.

private void LoadOnDemandModule(string ModuleName, string ViewName, string RegionName)
        {
           
            this._moduleManager.LoadModule(ModuleName);

            var myView = ServiceLocator.Current.GetInstance<IModule>(ViewName);
            var currentView = this._regionManager.Regions[RegionName].Views.Contains(myView);

            if (currentView)
            {
                this._regionManager.Regions[RegionName].Remove(myView);
            }

            this._regionManager.Regions[RegionName].Add(myView);
            this._regionManager.Regions[RegionName].Activate(myView);

           
        }

 

This code works fine in WPF. But its not working in Silverlight.

In my module, I exported the view like below. So I'm calling the view using the key "MyView1" in the shell and loading in the region. These all the things are working fine in WPF but its not working in Silverlight.

[Export("MyView1", typeof(IModule))]
    public partial class MyView1 : UserControl,IModule
    {
        public MyView1()
        {
            InitializeComponent();
        }

        #region IModule Members

        public void Initialize()
        {
           
        }

        #endregion

        #region IModule Members

        void IModule.Initialize()
        {
           
        }

        #endregion
    }

 

Please let me know where I'm wrong.

May 13, 2011 at 1:19 PM
Edited May 16, 2011 at 6:06 AM
Is your region a singleactive or allactive region? Since you are using mef just use regionmanager.requestnavigate. No need for adding/removing views from the region manually. Of course your view/viewmodel should implement inavigationaware. Read up on this.
May 16, 2011 at 6:02 AM

Hi,

private void LoadOnDemandModule(string ModuleName, string ViewName, string RegionName)
{

this._moduleManager.LoadModule(ModuleName);

var myView = ServiceLocator.Current.GetInstance<IModule>(ViewName);
var currentView = this._regionManager.Regions[RegionName].Views.Contains(myView);

if (currentView)
{
this._regionManager.Regions[RegionName].Remove(myView);
}

this._regionManager.Regions[RegionName].Add(myView);
this._regionManager.Regions[RegionName].Activate(myView);


}

The above colored line code only throws error "Activation error occured while trying to get instance of type IModule, key "MyView1". I don't know why I'm getting this error. Can u send me your mail id, I will send my sample code.

May 16, 2011 at 6:06 AM

You can send it to ganesh.shivshankar@gmail.com. I will be able to look at this only after I get home. Is that ok with you?

May 17, 2011 at 6:16 AM

Hi Ganesh,

Let me explain my project. We are creating CompositeUI(Dual Targeting) project. We suppose to create a function to support both SilverLight and WPF. Now if you see the project which I sent to you, will get some idea. The modulemanager.LoadModule works in WPF but the same functionality is not working in Silverlight. I just need why that same way of loading is not working. I debugged the code in WPF when the LoadModule calls it invokes the LoadModuleCompleted event. But in sliverlight it won't invoke the LoadModuleCompleted event.

Regards

Mahesh

May 17, 2011 at 6:19 AM

HI Mahesh,

The problem with your silverlight front is it doesnt seem to have the right path to your xap (module) your are downloading. If you hook up to LoadModuleCompleted event and check the value of 'e' you will get a "Invalid uri format" exception. Basically with silverlight it uses a WebClient to download the xap. So unless it does not have the right path to the xap it wont be able to download it. One way to overcome this is host your app on a ASP.NET website and place the xaps in the ClientBin. So LoadModule will automatically pick it up from there.

May 17, 2011 at 2:57 PM

Hi Mahesh,

In order to target your application for Silverlight and WPF, you can read Chapter 10: Sharing code Between Silverlight and WPF from Prism’s provided documentation.

On the other hand, you can load modules on demand in WPF and Silverlight applications. This topic is covered in Chapter 4: Modular Application Development, specifically in “Loading Modules on Demand” section.

Take into account that in Silverlight applications, modules are packaged into .xap files. For each .xap file, you will need to create a new Silverlight Application project. In Visual Studio 2008 and 2010, only application projects produce a separate .xap file.

I hope you find this information useful.

Thanks,

Miguel Bronzovic
http://blogs.southworks.net/mbronzovic

 

May 18, 2011 at 12:51 PM

Thanks a lot guys...I did it. I loaded the module dynamically into the shell. once again thanks very much!