Lazy-load views into a tabcontrol

Topics: Prism v4 - WPF 4
Oct 9, 2013 at 10:24 PM
I'm using Prism to loaded a series of views into a TabControl. I'm able to get each view to appear as a separate tab by using either the RequestNavigate or RegisterViewWithRegion method, with the TabControl defined as the target region. However, in both cases, the view and its corresponding viewmodel are instantiated immediately when the application is started. I'd like the view and its viewmodel to not be instantiated until its corresponding tab is clicked in the main TabControl.

I've tried defining the views in separate modules, and loading the modules with the InitializationMode.OnDemand parameter, but this doesn't seem to have any effect; I'm assuming this is because the RequestNavigate and RegisterViewWithRegion methods are enough to activate the module.

Is there a way to configure Prism to only instantiate and load a view/viewmodel when its associated tab is activated?

Here's a shortened version of my Shell.xaml:
<Window>
  <Grid>
    <TabControl prism:RegionManager.RegionName="TabRegion" />
  </Grid>
</Window>
And how I'm currently adding tabs to my TabControl region, in the InitializeShell() method of my Bootstrapper.cs:
regionManager.Regions["TabRegion"].RequestNavigate("FirstTabView");
regionManager.Regions["TabRegion"].RequestNavigate("SecondTabView");
regionManager.Regions["TabRegion"].RequestNavigate("ThirdTabView");
Any help would be much appreciated.
Oct 10, 2013 at 6:32 PM
Edited Oct 10, 2013 at 6:52 PM
When using a TabControl you would need to Register each View on the TabRegion in order to be the views available for Navigation. This way, the Views and ViewModels would get instantiated on the TabRegion.

However, you can control the load and activation of every view with View Discovery or View Injection.
RequestNavigate() should not be used for registering Views into Regions. Doing so, would make the Region to navigate the requested View as you correctly mentioned.

Instead, you can register the views with the following alternatives:
    // View discovery
    this.regionManager.RegisterViewWithRegion("TabRegion", typeof(FirstTabView));


    // View injection
    IRegion tabRegion = regionManager.Regions["TabRegion"];
    var tabView1 = container.Resolve<FirstTabView>();
    tabRegion.Add(tabView1, "FirstTabView");
This way, only the FirstTabView would become Active when your application loads.
As I mentioned before, notice that View and ViewModel instantiation would occur when registered to a Region, so the View gets available for navigation.


You can find more information on the following Prism Guide chapter:

If you don't want the Views get instantiated because somehow they would take a long time to load, but also have tabs available to navigate to, you could think of a custom approach:
One possible way would be to register blank views in the TabRegion, so you would be instantiating empty views at StartUp. And then, define a Child Region on each of these views where you would register your views by the time the corresponding tab is Navigated. You could Register and Activate your views on a CollectionChanged handler on each ViewModel when the active TabRegion View changes by subscribing to it as follows:
this.regionManager.Regions["TabRegion"].ActiveViews.CollectionChanged += ActiveViews_CollectionChanged;...

and verify is the corresponding view is already loaded:
void ActiveViews_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
        foreach (var item in e.NewItems)
        {
            FrameworkElement tabview = item as FrameworkElement;
            if (tabview != null && tabview.DataContext == this)
            {
                IRegion subRegion = this.regionManager.Regions[RegionNames.SubRegion];
                if (subRegion == null) return;

                // Check to see if we need to create an instance of the view.
                FirstTabView firstTabview = subRegion.GetView("FirstTabView") as FirstTabView;
                if (firstTabview == null)
                {
                    // Create a new instance of the View using the container.
                    firstTabview = this.container.Resolve<FirstTabView>();

                    // Add the view to the subregion. This automatically activates the view too.
                    subRegion.Add(firstTabview, "FirstTabView");
                }
                else
                {
                    // The view has already been added to the region so just activate it.
                    subRegion.Activate(firstTabview);
                }
            }
        }
    }   
}
I hope this helps,
Gabriel Ostrowsky
http://blogs.southworks.net/gostrowsky
Marked as answer by NathanFriend on 10/10/2013 at 11:03 AM
Oct 10, 2013 at 7:01 PM
Edited Oct 10, 2013 at 7:02 PM
Thanks for the thorough response. After reading your explanation, I think I'm going to implement the tab interface as a set of RadioButton s and then manually navigate to the appropriate views in a separate region, rather than trying to work around the default behavior of the TabControl.