Dynamic views in TabControl - ViewName?

Topics: Prism v4 - WPF 4
Mar 7, 2011 at 8:38 AM

Hello,

I have a view which contains a TabControl region: 

            <TabControl Margin="5" x:Name="DossierLeftContentRegion" cal:RegionManager.RegionName="{x:Static inf:RegionNames.DossierLeftContentRegion}">
                <TabControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ViewName}"></TextBlock>
                    </DataTemplate>                    
                </TabControl.ItemTemplate>                
            </TabControl>

 

I have a controller class attached to dynamically add instances of a view to the region:

  foreach (DocumentSearchElement ds in DocumentDossierSettings.DocumentSearches)
            {
                string viewName = ds.Name;
                IRegion dossierRegion = this.regionManager.Regions[ds.RegionName];

                var view = dossierRegion.GetView(viewName) as DocumentSearchView;
                if (view == null)
                {
                    view = ServiceLocator.Current.GetInstance<DocumentSearchView>();                    
                    dossierRegion.Add(view, viewName);
                }

                DocumentSearchViewModel model = view.DataContext as DocumentSearchViewModel;
                model.DocumentSearchSettings = ds;
                model.DependencyDocument = document;
                model.InitialSearch();
            }


This works basically, but I want to display the view names on the tabs. With the code below (Binding ViewName) they are empty. How to get the view names to the tabs?


Best Regards

Andreas 

Developer
Mar 9, 2011 at 2:16 PM
Edited Mar 9, 2011 at 3:17 PM

Hi Andreas,

The binding probably isn't working because, when you use a binding inside a DataTemplate, the DataContext is implicitly set to the containing element, so you can't access your ViewModel or controller (which should be set as the DataContext for your view).

You might find the suggestions in these threads useful to achieve your scenario:

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 9, 2011 at 2:44 PM
Edited Mar 9, 2011 at 3:25 PM

Hi Guido,

thanks for your reply. My problem is, that I don't know WHERE I can find the view name I've passed to the region manager when adding the view.

 

dossierRegion.Add(view, viewName);

 


I would like to avoid adding a property (e.g. ViewName) to my view model and set this as well, because I already specified that on the region manager when adding the view (code above). Isn't it possible to get the value directly from the region manager?

Update: Even if I add a property "ViewName" to my DocumentSearchViewModel it isn't displayed. As I can see, the same approach is used on UICompositionQuickStart (EmployeeSummaryView). I cannot figure out why in my case the Binding to ViewName doesn't work. The TabControl/ItemContainerStyle seems to don't have any DataContext assigned?!


Best Regards

Andreas

Developer
Mar 9, 2011 at 3:25 PM
Edited Mar 9, 2011 at 3:25 PM

Hi Andreas,

What you're mentioning isn't possible out of the box. The name you're assigning to that instance of your view is used internally in the region manager, and is placed inside class named ItemMetadata (which contains the view and its associated metadata, such as the name), which is stored in a protected ObservableCollection named ItemMetadataCollection in the corresponding Region object.

The approach I would recommend is to add a property, as you've mentioned.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 9, 2011 at 3:31 PM

Guido: Thanks again, please see my recent update:

Update: Even if I add a property "ViewName" to my DocumentSearchViewModel it isn't displayed. As I can see, the same approach is used on UICompositionQuickStart (EmployeeSummaryView). I cannot figure out why in my case the Binding to ViewName doesn't work. The TabControl/ItemContainerStyle seems to don't have any DataContext assigned?! 

Developer
Mar 9, 2011 at 3:36 PM

Andreas,

Have you tried adding the ViewModel as a resource and specifying the source in the binding, as it is done in this thread?

In case you have, and you still experience this problem, it could be helpful if you could provide us with further information, or a repro sample of your solution, so that we can help you further with your issue.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 9, 2011 at 4:21 PM

No success yet. The Silverlight implementation of UICompositionQuickStart uses TabControlRegionAdapter to label the tab:

 

<prism:TabControlRegionAdapter.ItemContainerStyle>
	<Style TargetType="sdk:TabItem">
		<Setter Property="HeaderTemplate">
			<Setter.Value>
                         <!--Display the child view name on the tab header-->
				<DataTemplate>
					<TextBlock Text="{Binding ViewName}" />
				</DataTemplate>
			</Setter.Value>
		</Setter>
	</Style>
</prism:TabControlRegionAdapter.ItemContainerStyle>

Does the RegionAdapter provide the DataContext (= the ViewModel of the view placed in the tab?

Do I need something similar in WPF?

Does anyone use TabControl in WPF as Region? How do get your tabs labeled??

Developer
Mar 9, 2011 at 4:54 PM
Edited Mar 9, 2011 at 4:54 PM

You might find the following thread useful, where a similar issue is addressed:

How to set tabItem header when UI element region is TabControl

As shown in this other thread, this approach is also used on the StockTrader Reference Implementation.

I hope you find this helpful.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Mar 9, 2011 at 5:05 PM

Thank you so much, Guido - its working!

Mar 11, 2011 at 4:02 PM

One more problem:

If I use this approach to set the view name:
 

<TabControl ...>

  <TabControl.ItemContainerStyle>

           <Style TargetType="TabItem">
                   <Setter Property="Header" Value="{Binding DataContext.ViewName}"></Setter>
            </Style>
        </TabControl.ItemContainerStyle>
</TabControl>

 

 

The external style definition from ResourceDictionary for TabItem (DynamicResource) isn't applied. So by setting the Header property declarative as shown above, all other style definitions for the TabItem from ResourceDictionary are missng.
How to work around this? Is there another way of setting the header without using ItemContainerStyle?

Developer
Mar 11, 2011 at 5:01 PM

Hi,

You could try specifying the aforementioned setter for the Header in your external style definition.

You might find the following resources useful to achieve your requirement:

I hope you find this helpful.

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi

Mar 14, 2011 at 10:27 AM

Thanks Guido, got it working!

Mar 23, 2011 at 3:26 PM

Well, I have added the following to my Resource Dictionary - TabItem Template (ExpressionDark.xaml file):

 

 			    <TextBlock Padding="10" Text="{Binding DataContext.ViewName}" FontWeight="Bold">
                                <TextBlock.Style>
                                    <Style TargetType="TextBlock">
                                       <Style.Triggers>
                                          <DataTrigger Binding="{Binding DataContext.ViewName}" Value="{x:Null}">
                                    <Setter Property="Visibility" Value="Collapsed"/>
                                </DataTrigger>
                                       </Style.Triggers>
                                    </Style>
                                </TextBlock.Style>
                           
                            </TextBlock>
                            
<ContentPresenter x:Name="ContentSite" RecognizesAccessKey="True" ContentSource="Header" d:LayoutOverrides="Width, Height" HorizontalAlignment="Center" Margin="6,1,6,1" VerticalAlignment="Center" />

 

So, in case of the Prism TabControl region, when a ViewName is present, it is displayed, else the default tab item header is displayed (= "normal" TabControl which doesn't act as region). Everything works fine so far.
This whole thing works for the Expression Dark theme, since I have all the XAML definitions in my application, and simply extended the definitions as shown above, but of course it is missing when I use the built-in Aero theme instead:

       <ResourceDictionary Source="/PresentationFramework.Aero;component/themes/Aero.NormalColor.xaml"/>

Using these style definitions from assembly, I cannot simply add the ViewName extension for my TabControl regions, so the tab headers are empty again. Since I have no access to the XAML I don't know how to extend the Aero theme in order to display the ViewName. How to solve that??

 

Best Regards

Developer
Mar 23, 2011 at 4:34 PM

Hi Andreas,

One possible way you could try for achieving your scenario would be to include the style you've implemented to show the view's name in the header as a resource of the view in which you're showing your tab control, and reference it as a static resource in the control's style property.

I hope you find this helpful.

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi

Mar 23, 2011 at 5:35 PM
Edited Mar 23, 2011 at 5:51 PM

Hi Guido,

Thanks for replying, but I'm afraid its not appropriate to use the whole Style definition for the Template property of TabItem inside of the View. First of all its specific to the ExpressionDark theme (so it should be different at all when using another theme), and it would reside in different Views, since there are many TabControl regions in my application.
So in the default template definition of ExpressionDark the following is displaying the Tab Header.  

 <ContentPresenter x:Name="ContentSite" RecognizesAccessKey="True" ContentSource="Header" d:LayoutOverrides="Width, Height" HorizontalAlignment="Center" Margin="6,1,6,1" VerticalAlignment="Center" />

The problem is that the ContentSource="Header" is empty in case of using the TabControl as region. Isn't there any chance to get it set with the ViewName?? Then it would work out of the box with any theme without adding some style extensions... I've tried to add a Header property to the View (for testing purposes) which returing a string value, but the tab header remains empty. Shouldn't that work? Since my TabItem is a instance of my view, the ContentPresenter should look for a "Header" property at the View, or do I go wrong?

 

Best Regards

Andreas 

Mar 23, 2011 at 6:49 PM

Hi Andreas,

In the UI Composition QuickStart provided with Prism, you will find how you can set the name of the tab item. Specifically, in the EmployeeSummaryView.xaml which uses a tabcontrol and EmployeeDetailsViewModel where a ViewName property is defined.

As this is not strictly related to Prism, you might find better support in WPF Forums.

Thanks,

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

 

Mar 23, 2011 at 7:17 PM

Hi Miguel,

I know this sample (please see my post above), but doing so (setting the Header text through Style definition), my theme style definitions get lost. I am using the ExpressionDark.xaml style definitions for my application, and if I set the tab region header as done in UI Composition Quickstart, the tabs are not shown in the correct "ExpessionDark" design anymore (as defined in ExpressionDark.xaml), since I override the TabItem style within the View... So the tabs are shown in default style, but having the correct header text.
So, as suggested by Guido, I took a little extension to the ExpressionDark.xaml in order do handle my ViewName property and display it (if existing). This works, but if I now want to use another external Theme (e.g. Aero), this stuff isn't contained of course (and I think I cannot extend it - i don't have access to the XAML at all), so the tab headers remain empty..

Guys, really nobody uses TabControl regions with PRISM and custom TabControl styles / different themes in background??

 

Best Regards

Andreas

Mar 24, 2011 at 11:42 AM

Hi,

Finally I got a solution. Now I set the TabControl.ItemTemplate inside my view in order to display the View name. The challenge here is to get the right Binding to the ViewName property (which resides in the ViewModel). I use the following:

 <TabControl Margin="5" x:Name="DossierRightContentRegion" cal:RegionManager.RegionName="{x:Static inf:RegionNames.DossierRightContentRegion}" Grid.Column="1">
                <TabControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Content.DataContext.ViewName, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=TabItem}}"></TextBlock>
                    </DataTemplate>
                </TabControl.ItemTemplate>
            </TabControl>

So I navigate to the TabItem using FindAncestor, and going through Content (= view) to the ViewModel's property..  
Thats working properly, independed from the used theme / merged style dictionaries used for the app..

 

Best Regards

Andreas

Developer
Mar 28, 2011 at 2:27 PM

Andreas,

I'm glad that you've found a solution to achieve your requirement. Thank you for sharing your insight with the rest of the community, as this might help other users pursuing similar scenarios.

Thanks,

Guido Leandro Maliandi 
http://blogs.southworks.net/gmaliandi