[Prism 5] RegionName in ItemsControl DataTemplate

May 6, 2014 at 4:12 PM
Edited May 7, 2014 at 6:32 PM
Hi,
I'm working on a composite application in WPF using Prism 5.

User, needs to enter his login and password, than, the application load modules dynamically related to the user account type. ( root, admin, simple user)
Each loaded module has a category, Example:
Administration Category :
  • Users
  • Application Preferences
  • ...
I want to get all categories on a ListBox in the left of my window, when Selection changed on my Categories ListBox, then show the related TabControl on the right with the specified items (Loaded modules)

As the following:
Schema

My Category and Module Classes are defined as the followng:
public class Category
    {
        private readonly ObservableCollection<Module> modules = new ObservableCollection<Module>();
        /// <summary>
        /// The category name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Modules List
        /// </summary>
        public ObservableCollection<Module> Modules
        {
            get { return modules; }
        }
    }

    public class Module
    {
        /// <summary>
        /// The module name
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// The module header on the tabControl
        /// </summary>
        public string Header { get; set; }
    }

View:

<Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="160" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Button Width="120" Height="40" Content="Initialize" Command="{Binding InitializeCategoriesCommand}" />
        <ListBox x:Name="CategoriesListBox" Grid.Row="1" Grid.Column="0" 
                 ItemsSource="{Binding Categories}" SelectedIndex="{Binding SelectedIndex}" SelectionChanged="Selector_OnSelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" TextAlignment="Center" FontSize="30" VerticalAlignment="Center"
                               TextWrapping="Wrap" Padding="10" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ItemsControl x:Name="TabControlsItems" 
                      ItemsSource="{Binding Categories}"
                      Grid.Row="1" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
                      >
            <ItemsControl.ItemContainerStyle>
                <Style TargetType="{x:Type ContentPresenter}">
                    <Setter Property="Visibility" Value="Collapsed"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <TabControl ItemsSource="{Binding Modules}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  >
                        <TabControl.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Header}" />
                            </DataTemplate>
                        </TabControl.ItemTemplate>
                        <TabControl.ContentTemplate>
                            <DataTemplate>
                                <StackPanel>
                                    <TextBlock Text="Module Content" FontSize="30" />
                                    <TextBlock Text="{Binding Name}" FontSize="20" />
                                </StackPanel>
                            </DataTemplate>
                        </TabControl.ContentTemplate>
                    </TabControl>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

ViewModel:

private readonly ObservableCollection<Category> categories = new ObservableCollection<Category>();
        private IModuleManager moduleManager;
        private IRegionManager regionManager;

        public MainViewModel(IRegionManager regionManager, IModuleManager moduleManager)
        {
            this.regionManager = regionManager;
            this.moduleManager = moduleManager;
            InitializeCategoriesCommand = new DelegateCommand(InitializeCategories);
        }

        private void InitializeCategories()
        {
            Categories.Clear();
            for (var i = 1; i < 5; i++)
            {
                var cat = new Category("Cat " + i);
                cat.InitializeModules();
                Categories.Add(cat);
            }
        }

        public ObservableCollection<Category> Categories
        {
            get { return categories; }
        }

        public DelegateCommand InitializeCategoriesCommand { get; private set; }

The Code above give the following results and it works perfectly:

Image

and

Image

Modules:

In each module i associate the main view to a region that has the same name as my Category.
RegionManager.RequestNavigate("CategoryName", "ModuleMainView");

My Question:

How can i add a region name in my itemscontrol datatemplate and associate it to a TabControl?
I tried to do the following:
<TabControl prism.RegionManager.RegionName="{Binding Name}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  />
To bind directly the categoryName, this allows Prism to add views from loaded models directly in a new item in the TabControl. but after hours of googling, i found this post where Damian explains that the region is created but never registered! i tried also to use his code, but his solution doesn't work on prism 5,
Any suggestions please?
May 7, 2014 at 6:06 PM
Edited May 7, 2014 at 6:07 PM
Hi,

I have just updated to Prism 5 Damian's workaround for the WPF solution, which is specified in this blogpost.
You may find the working sample in the following OneDrive link:

Hope this helped,
Regards.

Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky
Marked as answer by cYounes on 5/7/2014 at 11:32 AM
May 7, 2014 at 6:30 PM
Edited May 7, 2014 at 7:54 PM
Thanks GOstrowsky You saved my life :) :)

I've just downloaded your code, and it works perfectly, however, it adds a view one time, for the second it raises the following exception:

Exception

I solved the problem by adding a new static variable to avoid getting the same views names in the the region manager as the following:
private static int viewCounter;

        public HelloWorldViewModel()
        {
            AddView = new DelegateCommand
                (
                () =>
                    {
                        viewCounter++;
                        ServiceLocator.Current.GetInstance<IRegionManager>().Regions["RegionInsideDataTemplate"].Add(
                            string.Format("View {0} added through View Injection", viewCounter));
                    }
                );

            MyRegionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
            RaisePropertyChanged("MyRegionManager");
        }
Thanks a lot :)
May 7, 2014 at 6:44 PM
Hi,

Thank you for the feedback. I will add it to the sample solution.

I am glad I helped you. As I commented you on Damian's post, I will take a look into the Silverlight solution and migrate it to WPF in order to update it to Prism 5.

Regards.
Gabriel Ostrowsky
https://blogs.southworks.net/gostrowsky