wpfxamlbindingtabcontrolattached-properties

XAML: How to bind TabControl nested element to Attached Property of TabItem


I have been searching for a "pure" XAML solution for this problem but just cannot find it. My goal would be to only create an Attached Property in code behind but the rest should be XAML only without creating a Custom Control or User Control. But I'm not sure whether this is possible at all and if, how to make the connection between a nested element inside the TabControl template and an Attached Property set in a TabItem

I'd have a boilerplate Attached Property of string with [AttachedPropertyBrowsableForType(typeof(TabItem))] and/or [AttachedPropertyBrowsableForType(typeof(TabControl))] inside my MainWindow class and the following XAML

<Window x:Class="AP_Test.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AP_Test"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="800">

  <Window.Resources>
    <Style TargetType="{x:Type TabControl}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TabControl}">
            <Grid Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
              <Grid.ColumnDefinitions>
                <ColumnDefinition Name="ColumnDefinition0"/>
                <ColumnDefinition Name="ColumnDefinition1" Width="0"/>
              </Grid.ColumnDefinitions>
              <Grid.RowDefinitions>
                <RowDefinition Name="RowDefinition0" Height="Auto"/>
                <RowDefinition Name="RowDefinition1" Height="*"/>
              </Grid.RowDefinitions>
              <TabPanel Name="headerPanel" 
                                  Background="Transparent" 
                                  Grid.Column="0" 
                                  IsItemsHost="true" 
                                  Margin="2,2,2,0" 
                                  Grid.Row="0" 
                                  KeyboardNavigation.TabIndex="1" 
                                  Panel.ZIndex="1"/>
              <Border Name="contentPanel" 
                                BorderBrush="{TemplateBinding BorderBrush}" 
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}" 
                                Grid.Column="0" 
                                KeyboardNavigation.DirectionalNavigation="Contained" 
                                Grid.Row="1" 
                                KeyboardNavigation.TabIndex="2" 
                                KeyboardNavigation.TabNavigation="Local">
                <DockPanel Background="White">
                  <Grid Name="TabControlHeader" DockPanel.Dock="Top" Height="65">
                    <Label x:Name="SelectedItemTitle" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" Content="How to bind to AP ItemTitle?"/>
                  </Grid>

                  <Grid Name="Detail" Margin="8,0,8,8">
                    <Border BorderThickness="3,3,0,0" BorderBrush="DarkGray"  CornerRadius="3"/>
                    <Border BorderThickness="2,2,1,1" BorderBrush="LightGray" CornerRadius="3"/>
                    <Border BorderThickness="1,1,1,1" BorderBrush="White" CornerRadius="3" Margin="3,3,-1,-1" Padding="5">
                      <Viewbox>
                        <ContentPresenter Name="PART_SelectedContentHost" 
                                                              ContentSource="SelectedContent" 
                                                              Margin="{TemplateBinding Padding}" 
                                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                      </Viewbox>
                    </Border>
                  </Grid>
                </DockPanel>
              </Border>
            </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </Window.Resources>
  
  <TabControl x:Name="TabCtl">
    <TabItem Header="Tab1" local:MainWindow.ItemTitle="Tab1 Title" />
    <TabItem Header="Tab2" local:MainWindow.ItemTitle="Tab2 Title" />
    <TabItem Header="Tab3" local:MainWindow.ItemTitle="Tab3 Title" />
  </TabControl>
</Window>

I'd like the respective title entries to be displayed in the TabControl's SelectedItemTitle label.

Any hints appreciated, even a definitive "That's not possible" would be good to know, so I can stop trying 😁


Solution

  • The property (sub-)path for an attached property needs to be enclosed in parentheses:

    Content="{Binding Path=SelectedItem.(local:MainWindow.ItemTitle),
                      RelativeSource={RelativeSource AncestorType=TabControl}}"
    

    See PropertyPath for Objects in Data Binding for details.


    An attached property is not even required. You could as well use the TabItem's Tag property like

    <TabItem Header="Tab1" Tag="Tab1 Title"/>
    

    with

    Content="{Binding Path=SelectedItem.Tag,
                      RelativeSource={RelativeSource AncestorType=TabControl}}"