Create TabItems with differing content at runtime based on templates in WPF

Alex Marshall picture Alex Marshall · Dec 29, 2009 · Viewed 7.3k times · Source

I'm writing an application with WPF and part of it involves managing for the user various files which are used configure custom, in-house devices. I need to be able to manipulate different types of configurations in tabs in the same TabControl, meaning that the content of the TabItems must be dynamically generated. I'd like to do this with ControlTemplates, but I haven't been successful in getting a working template yet. I have a ControlTemplate called "pendantConfigurationTabItemTemplate" defined in my Window resources, and I use the following code to apply the template (which contains a named item I need to access) to the TabItems and add them to their parent TabControl :

<ControlTemplate x:Key="pendantConfigurationTabItemTemplate" TargetType="TabItem">
    <StackPanel Orientation="Vertical">
        <my:PendantConfigurationFileEditor x:Name="configurationEditor"/>
        <StackPanel Style="{StaticResource defaultOkCancelButtonsContainerStyle}">
            <Button Style="{StaticResource defaultOkCancelButtonStyle}"/>
            <Button Style="{StaticResource defaultOkCancelButtonStyle}" Click="OkButton_Click"/>
        </StackPanel>
    </StackPanel>
</ControlTemplate>

Code behind :

TabItem ConfigTab = new TabItem();

switch (ConfigFile.Device)
{
  case DeviceType.PENDANT:
{
  ControlTemplate TabTemplate = Resources["pendantConfigurationTabItemTemplate"] as ControlTemplate;

  ConfigTab.Template = TabTemplate;
  ConfigTab.ApplyTemplate();

  object Editor = TabTemplate.FindName("configurationEditor", ConfigTab);

  PendantConfigurationFileEditor ConfigFileEditor = Editor as PendantConfigurationFileEditor;

  ConfigFileEditor.PendantConfiguration = DeviceConfig;

  break;
}
default:
  /* snipped */
  return;
}

ConfigTab.Header = ConfigFile.ConfigurationName;

this.EditorTabs.Items.Add(ConfigTab);
this.EditorTabs.SelectedIndex = this.EditorTabs.Items.Count - 1;

However, whenever I run the program, no tabs get added to the tab control, instead the tab control (seemingly) gets replaced or covered by the content of the template. Can somebody please help me out with this ?

Effectively, what I want to do is use the WPF templates as TabItem factories

Answer

itowlson picture itowlson · Dec 29, 2009

TabControl.ItemsSource plus DataTemplates is effectively the "templates as factories" solution you are asking for, but it demands a slightly different approach to your current one.

Rather than writing procedural code to create and template TabItems and calling Items.Add, use the ItemsSource property and data binding. This will cause WPF to create a TabItem for each object in the ItemsSource. You can then use ContentTemplateSelector to select appropriate templates for the object displayed on this tab, according to whatever criteria are appropriate (e.g. the Device property) -- though in this case you will be using DataTemplates rather than ControlTemplates.

Your selector will look something like this:

public class DeviceTypeSelector : DataTemplateSelector
{
  public DataTemplate PendantTemplate { get; set; }
  public DataTemplate DefaultTemplate { get; set; }

  public override SelectTemplate(object item, DependencyObject container)
  {
    ConfigFile cf = (ConfigFile)item;
    switch (cf.Device)
    {
      case DeviceType.Pendant: return PendantTemplate;
      default: return DefaultTemplate;
    }
  }
}

and will be instantiated in XAML like this:

<local:DeviceTypeSelector x:Key="dts"
                          PendantTemplate="{StaticResource pt}"
                          DefaultTemplate="{StaticResource dt}" />

(where pt and dt are suitable DataTemplates defined elsewhere in the resources).

Finally, your TabControl will look like this:

<TabControl Name="EditorTabs"
            ContentTemplateSelector="{StaticResource dts}" />

and you set it up as EditorTabs.ItemsSource = myConfigFiles; (or better still let it acquire the ItemsSource in XAML from the DataContext).

You'll also want to set up the headers of the TabItems: to do this, use TabControl.ItemContainerStyle, with a Setter for the Header property. I think this would look something like this:

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

(You can also inline the ContentTemplateSelector, by the way: I broke it out into a resource mostly so as to show things in smaller chunks.)