This is a migrated thread and some comments may be shown as answers.

Unexpected behavior for nested TabControls

10 Answers 372 Views
TabControl
This is a migrated thread and some comments may be shown as answers.
Alan Cordner
Top achievements
Rank 1
Alan Cordner asked on 24 Feb 2010, 04:25 PM
I have created a small application as a prototype of a larger application I need to build to test some of the functionality of nesting one TabControl within another. For the most part, things seems to work very well. However, I am encountering some strange behavior that I have spent days trying to resolve unsuccessfully. Here's a description of my app:

The outer TabControl is bound to an ObservableCollection of product groups, so I have a tab for each group. Selecting any one of these tabs is supposed to display as its content another TabControl which is bound to an ObservableCollection of products that belong to that group. Selecting any one of these tabs is supposed to display product-specific information and allow the user to select from various configuration options that apply to that product using a ComboBox. Up to this point, things seem to work just fine - all bindings seems to be doing what they are supposed to do.

Now, I would like each group tab to keep track of the last selected product on that tab, and each product tab to keep track of the last selected configuration on that tab, so if I change to another group or product tab, then come back to the original tab, the same product is still selected and the selected configuration is also still selected. Each group tab should function this way. So I added a SelectedProduct property to my Group class and bound it to the SelectedItem property of the product TabControl, and added a SelectedConfiguration property to my Product class and bound it to the SelectedItem property of the ComboBox on the product tab.
 
This is where the unexpected behavior begins. When I run the app, the first group tab is selected, and the first product tab in that group is also selected as expected and desired. However, when I click another group tab, none of the products are selected on that tab (i.e. none of the tabs have the appearance of being selected) although the content of the product tab indicates the first tab is actually selected. If I select another product tab, the correct product information is displayed, but if I select another group tab, then come back to the previous group tab again, the product selection has changed and the selected configuration has been lost.

I'm pretty sure this is all related to the order in which bindings occur and events are raised, but I have not been able to succeed in getting it to work as desired. Maybe what I am trying to accomplish is not possible using only XAML and I need to add more code behind for the SelectionChanged events of the TabControls? Suggestions?

Here is my complete project code combined into a single file and simplified as much as I can. I've wired the SelectionChanged event of each TabControl just to try to see what is happening.

Interfaces and Classes:
using System;  
using System.Collections.ObjectModel;  
using System.Linq;  
using System.Text;  
using System.Windows.Media.Imaging;  
 
namespace NestedTabControlBinding  
{  
    public interface IGroup  
    {  
        string Name { getset; }  
        BitmapImage Image { getset; }   
        ObservableCollection<IProduct> Products { get; }  
        IProduct SelectedProduct { getset; }  
    }  
 
    public interface IProduct  
    {  
        string BaseModel { getset; }  
        BitmapImage Image { getset; }  
        ObservableCollection<IConfiguration> Configurations { get; }  
        IConfiguration SelectedConfiguration { getset; }  
    }  
 
    public interface IConfiguration  
    {  
        string Name { getset; }  
        string Voltage { getset; }  
        string Frequency { getset; }  
    }  
 
    public class Group : IGroup  
    {  
        private const string _imagePath = "pack://application:,,,/NestedTabControlBinding;Component/Resources/Images/{0}";  
 
        private string _name;  
        BitmapImage _image;  
        private ObservableCollection<IProduct> _products = new ObservableCollection<IProduct>();  
        private IProduct _selectedProduct;  
 
        public Group(string name, string path)  
        {  
            Name = name;  
            Image = new BitmapImage(new Uri(path));  
 
            Products.Add(new CMyProduct(Name + " Model 1"string.Format(_imagePath, "Model1.jpg")));  
            Products.Add(new CMyProduct(Name + " Model 2"string.Format(_imagePath, "Model2.jpg")));  
            Products.Add(new CMyProduct(Name + " Model 3"string.Format(_imagePath, "Model3.jpg")));  
        }
        #region IGroup Members  
        public string Name  
        {  
            get { return _name; }  
            set { _name = value; }  
        }  
        public BitmapImage Image   
        {   
           get { return _image; }  
           set { _image = value; }  
        }   
        public ObservableCollection<IProduct> Products  
        {  
            get { return _products; }  
        }  
        public IProduct SelectedProduct   
        {  
            get { return _selectedProduct; }  
            set { _selectedProduct = value; }  
        }
        #endregion  
    }  
 
    public abstract class ProductBase : IProduct  
    {  
        private string _baseModel;  
        BitmapImage _image;  
        private ObservableCollection<IConfiguration> _configurations = new ObservableCollection<IConfiguration>();  
        private IConfiguration _selectedConfiguration;  
 
        public ProductBase(string baseModel, string path)  
        {  
            BaseModel = baseModel;  
            Image = new BitmapImage(new Uri(path));  
        }
        #region IProduct Members  
        public string BaseModel  
        {  
            get { return _baseModel; }  
            set { _baseModel = value; }  
        }  
        public BitmapImage Image  
        {  
            get { return _image; }  
            set { _image = value; }  
        }   
        public abstract string Description { get; }  
        public System.Collections.ObjectModel.ObservableCollection<IConfiguration> Configurations  
        {  
            get { return _configurations; }  
        }  
 
        public IConfiguration SelectedConfiguration  
        {  
            get { return _selectedConfiguration; }  
            set { _selectedConfiguration = value; }  
        }
        #endregion  
    }  
 
    public class Configuration : IConfiguration  
    {  
        private string _Name;  
        private string _Voltage;  
        private string _Frequency;  
 
        public Configuration(string name, string voltage, string frequency)  
        {  
            Name = name;  
            Voltage = voltage;  
            Frequency = frequency;  
        }
        #region IConfiguration Members  
        public string Name  
        {  
            get { return _Name; }  
            set { _Name = value; }  
        }  
        public string Voltage  
        {  
            get { return _Voltage; }  
            set { _Voltage = value; }  
        }  
        public string Frequency  
        {  
            get { return _Frequency; }  
            set { _Frequency = value; }  
        }
        #endregion  
    }  
 
    public class CMyProduct : ProductBase  
    {  
        private const string _description = "This product has a base model of ";  
 
        public CMyProduct(string baseModel, string imagePath)  
            : base(baseModel, imagePath)  
        {  
            Configurations.Add(new Configuration(BaseModel + "-1""110VAC""50Hz"));  
            Configurations.Add(new Configuration(BaseModel + "-2""220VAC""60Hz"));  
            Configurations.Add(new Configuration(BaseModel + "-3""110VAC""50Hz"));  
            Configurations.Add(new Configuration(BaseModel + "-4""220VAC""50Hz"));  
            Configurations.Add(new Configuration(BaseModel + "-5""110VAC""60Hz"));  
        }  
 
        public override string Description  
        {  
            get { return _description + BaseModel + "."; }  
        }  
    }  
 
    public class GroupsViewModel  
    {  
        private const string _imagePath = "pack://application:,,,/NestedTabControlBinding;Component/Resources/Images/{0}";  
 
        private ObservableCollection<IGroup> _groups = new ObservableCollection<IGroup>();  
        private IGroup _selectedGroup;  
 
        public GroupsViewModel()  
        {  
            Groups.Add(new Group("MetrologyWells"string.Format(_imagePath,"MetWell.jpg")));  
            Groups.Add(new Group("DryWells"string.Format(_imagePath, "DryWell.jpg")));  
            Groups.Add(new Group("Baths"string.Format(_imagePath, "Bath.jpg")));  
        }  
 
        public ObservableCollection<IGroup> Groups  
        {  
            get { return _groups; }  
        }  
        public IGroup SelectedGroup  
        {  
            get { return _selectedGroup; }  
            set { _selectedGroup = value; }  
        }  
    }  
}  
 

Window XAML:
<Window x:Class="NestedTabControlBinding.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:diagnostics="clr-namespace:System.Diagnostics;assembly=WindowsBase" 
    xmlns:vm="clr-namespace:NestedTabControlBinding" 
    xmlns:telerikNavigation="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.Navigation" 
    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"   
    Title="Nested TabControl Binding Example" Height="480" Width="800">  
 
    <Window.Resources> 
        <vm:GroupsViewModel x:Key="GroupsDataSource"/>  
    </Window.Resources> 
 
    <Grid x:Name="grid" DataContext="{StaticResource GroupsDataSource}">  
        <Grid.RowDefinitions> 
            <RowDefinition Height="40" /> 
            <RowDefinition Height="385*" /> 
            <RowDefinition Height="100*" /> 
        </Grid.RowDefinitions> 
 
        <StackPanel Grid.Row="1" Orientation="Vertical" > 
            <telerikNavigation:RadTabControl x:Name="tabGroups" MinWidth="630" MinHeight="200" MaxWidth="1024" MaxHeight="600" Margin="5" TabStripPlacement="Top"   
                                             VerticalAlignment="Stretch" HorizontalAlignment="Stretch" telerik:StyleManager.Theme="Vista"   
                                             SelectedItem="{Binding Path=SelectedGroup, Mode=TwoWay}" 
                                             SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}" 
                                             ItemsSource="{Binding Path=Groups}"   
                                             SelectionChanged="tabGroups_SelectionChanged">  
 
                <telerikNavigation:RadTabControl.ItemContainerStyle> 
                    <Style TargetType="telerikNavigation:RadTabItem">  
                        <Setter Property="HeaderTemplate">  
                            <Setter.Value> 
                                <DataTemplate> 
                                    <StackPanel Orientation="Horizontal">  
                                        <Image Source="{Binding Path=Image}" Margin="3" Height="32" Width="32"/>  
                                        <TextBlock Text="{Binding Path=Name}" Foreground="Black" Margin="3" VerticalAlignment="Center"/>  
                                    </StackPanel> 
                                </DataTemplate> 
                            </Setter.Value> 
                        </Setter> 
                        <Setter Property="ContentTemplate">  
                            <Setter.Value> 
                                <DataTemplate> 
                                    <StackPanel Orientation="Vertical">  
                                        <TextBlock Margin="10" FontSize="14" FontWeight="Bold">Choose a product from the tabs below:</TextBlock> 
                                        <telerikNavigation:RadTabControl x:Name="tabProducts" MinWidth="620" MinHeight="200" Margin="5" TabStripPlacement="Left"   
                                                 VerticalAlignment="Stretch" HorizontalAlignment="Stretch" telerik:StyleManager.Theme="Vista"   
                                                 SelectedItem="{Binding Path=SelectedProduct, Mode=TwoWay}" 
                                                 SelectedIndex="{Binding Path=SelectedIndex, Mode=TwoWay}" 
                                                 ItemsSource="{Binding Path=Products}" 
                                                 SelectionChanged="tabProducts_SelectionChanged">  
 
                                            <telerikNavigation:RadTabControl.ItemContainerStyle> 
                                                <Style TargetType="telerikNavigation:RadTabItem">  
                                                    <Setter Property="HeaderTemplate">  
                                                        <Setter.Value> 
                                                            <DataTemplate> 
                                                                <StackPanel Orientation="Vertical" > 
                                                                    <TextBlock Text="{Binding Path=BaseModel}" Margin="3" VerticalAlignment="Center"/>  
                                                                    <Image Source="{Binding Path=Image}" Margin="3" Height="32" Width="32"/>  
                                                                </StackPanel> 
                                                            </DataTemplate> 
                                                        </Setter.Value> 
                                                    </Setter> 
                                                    <Setter Property="ContentTemplate">  
                                                        <Setter.Value> 
                                                            <DataTemplate> 
                                                                <StackPanel Margin="5" Orientation="Vertical">  
                                                                    <StackPanel Orientation="Horizontal" > 
                                                                        <TextBlock Margin="2" FontWeight="Bold" FontSize="14" Text="Model"/>  
                                                                        <TextBlock Margin="2" FontWeight="Bold" FontSize="14" Text="{Binding Path=BaseModel}"/>  
                                                                        <TextBlock Margin="2" FontWeight="Bold" FontSize="14" Text="configurations: "/>  
                                                                        <ComboBox Margin="5" MinWidth="150" ItemsSource="{Binding Path=Configurations}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedConfiguration}"></ComboBox> 
                                                                    </StackPanel> 
 
                                                                    <StackPanel Orientation="Horizontal">  
                                                                        <TextBlock Margin="4" Text="Voltage: "/>  
                                                                        <TextBlock Margin="4" FontWeight="Bold" Text="{Binding Path=SelectedConfiguration.Voltage}" MinWidth="70"/>  
                                                                        <TextBlock Margin="4" Text="Frequency: "/>  
                                                                        <TextBlock Margin="4" FontWeight="Bold" Text="{Binding Path=SelectedConfiguration.Frequency}" MinWidth="70"/>  
                                                                    </StackPanel> 
                                                                    <TextBlock Margin="10" Text="{Binding Path=Description}"/>  
                                                                </StackPanel> 
                                                            </DataTemplate> 
                                                        </Setter.Value> 
                                                    </Setter> 
                                                </Style> 
                                            </telerikNavigation:RadTabControl.ItemContainerStyle> 
                                        </telerikNavigation:RadTabControl> 
                                    </StackPanel> 
                                </DataTemplate> 
                            </Setter.Value> 
                        </Setter> 
                    </Style> 
                </telerikNavigation:RadTabControl.ItemContainerStyle> 
            </telerikNavigation:RadTabControl> 
        </StackPanel> 
    </Grid> 
</Window> 
 

And the Code-Behind:
using System;  
using System.Collections.Generic;  
using System.Linq;  
using System.Text;  
using System.Windows;  
using System.Windows.Controls;  
using System.Windows.Data;  
using System.Windows.Documents;  
using System.Windows.Input;  
using System.Windows.Media;  
using System.Windows.Media.Imaging;  
using System.Windows.Navigation;  
using System.Windows.Shapes;  
using Telerik.Windows.Controls;  
 
namespace NestedTabControlBinding  
{  
    /// <summary>  
    /// Interaction logic for Window1.xaml  
    /// </summary>  
    public partial class Window1 : Window  
    {  
        public Window1()  
        {  
            InitializeComponent();  
        }  
 
        private void tabGroups_SelectionChanged(object sender, RoutedEventArgs e)  
        {  
            RadTabControl Tab = (RadTabControl)sender;  
 
            if (Tab.SelectedItem != null)  
            {  
                IGroup Group = (IGroup)Tab.SelectedItem;  
                Console.WriteLine("Selected group: " + Group.Name);  
            }  
            else 
            {  
                Console.WriteLine("Selected group: NONE");  
            }  
        }  
 
        private void tabProducts_SelectionChanged(object sender, RoutedEventArgs e)  
        {  
            RadTabControl Tab = (RadTabControl)sender;  
 
            if (Tab.SelectedItem != null)  
            {  
                IProduct Product = (IProduct)Tab.SelectedItem;  
                Console.WriteLine("Selected product: " + Product.BaseModel);  
            }  
            else 
            {  
                Console.WriteLine("Selected product: NONE");  
            }  
        }  
    }  
}  
 

I've attached the images also. They should be placed in the \Resources\Images folder of the project.

10 Answers, 1 is accepted

Sort by
0
Miro Miroslavov
Telerik team
answered on 26 Feb 2010, 03:17 PM
Hello Alan Cordner,

We've investigated your problem and it's an issue in the RadTabControl when dealing with ItemsSource. We'll try to fix it as soon as possible.

Greetings,
Miro Miroslavov
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Alan Cordner
Top achievements
Rank 1
answered on 26 Feb 2010, 03:42 PM
That's great news! So I'm not crazy after all!

I have one other issue with the application that I am developing, and it appears to be a binding issue, though I have not been able to determine what is causing the problem. The application I posted is a stripped down version of my real application just to show the nested TabControl issue. The issue I am having with my real application is that the header of each product tab (on the nested TabControl) is missing - the Image and TextBlock for the product model number do not appear on the tabs! It is strange because on the sample app I posted, everything works fine! And I have compared the sample app with my real app and I cannot find the difference that is causing this issue. It appears that the DataContext of the nested TabControl is not getting set as expected, although the content of each tab on the nested TabControl is correct.
0
Miroslav
Telerik team
answered on 04 Mar 2010, 01:53 PM
Hello Alan Cordner,

I first want to follow up on the first issue - we update the SelectedContent and the SelectedContentTemplate in correct order now and the nesting TabControls should be working as expected now.

As for the second issue: This is unexpected again.

I noticed that you are using styles to set the templates. While this is a valid use of styles they are set at slightly different time than the templates and something may be affected because of this. The TabCotnrol has two template properties: ItemTemplate - which is set as HeaderTemplate on the items and ContentTemplate which is set as ContentTemplate on the items (actually it is a fallback value).

Using these properties will result in less xaml and will possibly help you about this. Can you try it out and let us know if it works as expected then?

Greetings,
Miroslav
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Alan Cordner
Top achievements
Rank 1
answered on 04 Mar 2010, 03:24 PM
OK, I appreciate the quick response on the fix!

I am still very new to XAML and WPF, so I will attempt to use the Template properties and post my new project. In my large application, I had originally created templates for the the headers and contents of each tab, and everything worked fine. Then I must have changed something that caused the product tab headers to stop working, although I cannot determine what it was! After they stopped working, I combined everything back into the main XAML and got rid of the templates, but it did not fix the problem like I was hoping. I have compared my sample application and large application line by line in the XAML and code behind and the design of my classes and cannot determine why the sample works fine but my large app does not.

More soon...
0
Miroslav
Telerik team
answered on 04 Mar 2010, 03:41 PM
Hi Alan Cordner,

This is strange indeed. If you think that you can send us the application  (or enough to see the problem) we will be happy to look into why the Tab does not show its headers.

You can send it at miroslav.paskov [at] telerik.com as well.

Kind regards,
Miroslav
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Alan Cordner
Top achievements
Rank 1
answered on 04 Mar 2010, 07:07 PM
Would it be possible for you to modify my XAML to demonstrate how to setup these nested, bound tab controls using templates instead of styles? I have been working on it for a few hours now and have been unable to succeed in getting it to work.
0
Miroslav
Telerik team
answered on 04 Mar 2010, 11:21 PM
Hello Alan,

Here is the project that I used to test this.

I have used the template properties instead of Styles there.

Hopefully this will work as expected for you.

Best wishes,
Miroslav
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Alan Cordner
Top achievements
Rank 1
answered on 05 Mar 2010, 10:31 PM
OK, I have found the cause of the model number and image disappearing on the product (nested) TabControl. In my large application, my ProductBase class inherits from UIElement as well as implementing the IProduct interface (because I need to support routed events). If I remove the UIElement inheritance, the model number and image appear as expected on the tab headers! So the question is, why does inheriting from UIElement cause the binding in the product (nested) TabControl to fail?
0
Miro Miroslavov
Telerik team
answered on 09 Mar 2010, 01:10 PM
Hi Alan Cordner,

The problem here is that ItemsControl can not use UIElements as data objects to generate it's visual children (to create ContentPresenters for each item). For reference please find the attached project.
As ProductBase is a model class, rather than visual element it's better not to inherit from UIElement but DependencyObject.
Please let us know if you have further questions.

Sincerely yours,
Miro Miroslavov
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Alan Cordner
Top achievements
Rank 1
answered on 09 Mar 2010, 04:19 PM
OK, I just wanted to make sure that was known/expected behavior. I'll have to find another way to implement events on these objects.
Tags
TabControl
Asked by
Alan Cordner
Top achievements
Rank 1
Answers by
Miro Miroslavov
Telerik team
Alan Cordner
Top achievements
Rank 1
Miroslav
Telerik team
Share this question
or