none
Problem with binding TabControl to collection of UserControls

    Question

  • I want to bind a TabControl to collection of items inheriting from UserControl. The problem is the ItemTemplate of TabControl doesn't work when the items inherit from UserControl. Below is a simple application to test the situation. When TabItemDataControl inherits from UserControl the TabItems are blank. Remove inheritance to see the working case.

     

    Code Snippet

    using System.Collections.ObjectModel;
    using System.Windows.Controls;

    namespace TabControlUsingItemTemplate {
     
      public class TabItemDataControl : UserControl {

        private string _Header;
        private string _DataContent;

        public TabItemDataControl(string header, string dataContent) {
          _Header = header;
          _DataContent = dataContent;
        }

        public string Header {
          get { return _Header; }
        }

        public string DataContent {
          get { return _DataContent; }
        }

      }

      public class TabDataControlList : ObservableCollection {

        public TabDataControlList()
          : base() {

          Add(new TabItemDataControl("Header 1", "Content 1"));
          Add(new TabItemDataControl("Header 2", "Content 2"));
          Add(new TabItemDataControl("Header 3", "Content 3"));

        }

      }
    }

     

     

    Here is the test window:

    Code Snippet

    <Window x:Class="TabControlUsingItemTemplate.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:src="clr-namespace:TabControlUsingItemTemplate"
      Title="TabControlUsingItemTemplate" Height="300" Width="300" >

      <Window.Resources>
        <ObjectDataProvider x:Key="TabListResource" ObjectType="{x:Type src:TabDataControlList}" />

        <DataTemplate x:Key="HeaderTemplate">
          <TextBlock Text="{Binding Path=Header}" />
        </DataTemplate>

        <DataTemplate x:Key="ContentTemplate">
          <TextBlock Text="{Binding Path=DataContent}" />
        </DataTemplate>
      </Window.Resources>

      <DockPanel>
        <TabControl ItemsSource="{Binding Source={StaticResource TabListResource}}"
          ItemTemplate="{StaticResource HeaderTemplate}"
          ContentTemplate="{StaticResource ContentTemplate}"
        />
      </DockPanel>

    </Window>

     

     

    Thanks in advance

    Monday, April 07, 2008 6:34 PM

Answers

  •  

    I am not sure why ItemTemplate does not work the way you expected it to but you can still get around this problem by setting the ItemContainerStyle:

     

    Code Snippet

    <TabControl ItemsSource="{Binding Source={StaticResource TabListResource}}">

      <TabControl.ItemContainerStyle>

        <Style TargetType="{x:Type TabItem}">

          <Setter Property="Header">

            <Setter.Value>

              <Binding Path="Header"/>

            </Setter.Value>

          </Setter>

        </Style>

      </TabControl.ItemContainerStyle>

      <TabControl.ContentTemplate>

        <DataTemplate>

          <Button>

            <Binding Path="DataContent" />

          </Button>

        </DataTemplate>

      </TabControl.ContentTemplate>

    </TabControl>

     

     

    Hope this helps,

    Wells

     

     


    Wells Caughey | Magenic Technologies
    Tuesday, April 08, 2008 3:34 AM
  • Here is the implementation of HeaderedContentControl.PrepareHeaderedContentControl() method which will be called by the item container generator to generate container to visualize data item:


    internal void PrepareHeaderedContentControl(object item, DataTemplate itemTemplate, DataTemplateSelector itemTemplateSelector)

    {

        if (item != this)

        {

            // don't treat Content as a logical child

            ContentIsNotLogical = true;

            HeaderIsNotLogical = true;

     

            if (ContentIsItem || !HasNonDefaultValue(ContentProperty))

            {

                Content = item;

                ContentIsItem = true;

            }

     

            // Visuals can't be placed in both Header and Content, but data can

            if (!(item is Visual) && (HeaderIsItem || !HasNonDefaultValue(HeaderProperty)))

            {

                Header = item;

                HeaderIsItem = true;

            }

     

            if (itemTemplate != null)

                SetValue(HeaderTemplateProperty, itemTemplate);

            if (itemTemplateSelector != null)

                SetValue(HeaderTemplateSelectorProperty, itemTemplateSelector);

        }

        else

        {

            ContentIsNotLogical = false;

        }

    }



    This method will be called by TabControl's item container generator to generate TabItems. And you will notice that we cannot set the Header and Content to the single data item at the same time if the data item is a Visual (UserControl is a Visual dirative). So that the data item is only set to the Content property, that's why you don't see the header.



    hope this helps

    Monday, April 14, 2008 9:48 AM

All replies

  •  

    I am not sure why ItemTemplate does not work the way you expected it to but you can still get around this problem by setting the ItemContainerStyle:

     

    Code Snippet

    <TabControl ItemsSource="{Binding Source={StaticResource TabListResource}}">

      <TabControl.ItemContainerStyle>

        <Style TargetType="{x:Type TabItem}">

          <Setter Property="Header">

            <Setter.Value>

              <Binding Path="Header"/>

            </Setter.Value>

          </Setter>

        </Style>

      </TabControl.ItemContainerStyle>

      <TabControl.ContentTemplate>

        <DataTemplate>

          <Button>

            <Binding Path="DataContent" />

          </Button>

        </DataTemplate>

      </TabControl.ContentTemplate>

    </TabControl>

     

     

    Hope this helps,

    Wells

     

     


    Wells Caughey | Magenic Technologies
    Tuesday, April 08, 2008 3:34 AM
  • Thanks for your help, this solves my problem. But I still wonder what the problem is with the first case.
    Wednesday, April 09, 2008 3:57 PM
  • Here is the implementation of HeaderedContentControl.PrepareHeaderedContentControl() method which will be called by the item container generator to generate container to visualize data item:


    internal void PrepareHeaderedContentControl(object item, DataTemplate itemTemplate, DataTemplateSelector itemTemplateSelector)

    {

        if (item != this)

        {

            // don't treat Content as a logical child

            ContentIsNotLogical = true;

            HeaderIsNotLogical = true;

     

            if (ContentIsItem || !HasNonDefaultValue(ContentProperty))

            {

                Content = item;

                ContentIsItem = true;

            }

     

            // Visuals can't be placed in both Header and Content, but data can

            if (!(item is Visual) && (HeaderIsItem || !HasNonDefaultValue(HeaderProperty)))

            {

                Header = item;

                HeaderIsItem = true;

            }

     

            if (itemTemplate != null)

                SetValue(HeaderTemplateProperty, itemTemplate);

            if (itemTemplateSelector != null)

                SetValue(HeaderTemplateSelectorProperty, itemTemplateSelector);

        }

        else

        {

            ContentIsNotLogical = false;

        }

    }



    This method will be called by TabControl's item container generator to generate TabItems. And you will notice that we cannot set the Header and Content to the single data item at the same time if the data item is a Visual (UserControl is a Visual dirative). So that the data item is only set to the Content property, that's why you don't see the header.



    hope this helps

    Monday, April 14, 2008 9:48 AM
  • I am in a very similar situation (should I create a new thread instead of replying to this?).  Right now, I have a custom ObservableCollection that I set as the ItemsSource of the TabControl.  This works very well, and WPF makes it very easy, but I can't figure out how to work a custom tab item control into the mix.

    In my situation, I have:

    public class MyClass  
    {  
        private string _title;  
        private string _description;  
        public string Title  
        {  
            get { return _title; }  
        }  
        public string Description  
        {  
            get { return _description; }  
        }  
    }  
     
    public class MyTabCollection : ObservableCollection<MyClass>  
    {  
       // ...     


    In my XAML I have the following:

    <DataTemplate x:Key="HeaderTemplate">  
        <TextBlock Text="{Binding Path=Title}" TextTrimming="CharacterEllipsis" Width="100"/>  
    </DataTemplate> 
    <DataTemplate x:Key="ContentTemplate">  
        <TextBlock Text="{Binding Path=Description}" /> 
    </DataTemplate> 
    ...  
    <TabControl Grid.Column="1" 
        Name="tabMain" 
        ItemTemplate="{StaticResource HeaderTemplate}" 
        ContentTemplate="{StaticResource ContentTemplate}">  
    </TabControl> 
     

    This works perfectly fine for normal TabItem controls in that I can create an object of MyTabCollection, and set it as the ItemsSource for the TabControl and each of the MyClass objects will now be displayed as a TabItem.  I do not use the binding directly in XAML like the original poster (ObjectType = ... ) as i want to be able to swap collections in my code.

    MyTabCollection mtc = new MyTabCollection;  
    mtc.Add(new MyClass("Some Title0""Some Description0");  
    mtc.Add(new MyClass("Some Title1""Some Description1");  
     
    // This sets the collection on the TabControl and displays all of the tabs correctly.  
    tabMain.ItemsSource = mtc; 

    The issue occurs when I want the TabControl to use a custom control, deriving from a TabItem (public class MyTabItem : TabItem ).  This control was created in blend and is basically a TabItem control with an added button to the Header portion of the tab.  I can use it directly from XAML like so:

    <TabControl> 
        <mynamespace:MyTabItem Header="Header 1" /> 
        <mynamespace:MyTabItem Header="Header 2" /> 
    </TabControl> 


    but I cannot figure out how to get the TabControl to use the MyTabItem instead of TabItem in the first example, where I set the tabs using ItemsSource.  I see that the original poster had his class inherit from UserControl, but the class I am using is a bussiness object and unrelated to the UI.

    Thursday, September 04, 2008 5:34 PM