views:

173

answers:

3

Hello,

I have a scrolling TabControl, using a ScrollViewer and StackPanel (with the StackPanel set as IsItemsHost="true"). To begin with, I am working from a solution originally outlined here - Creating Scrolling Tabs Using WPF's TabControl . At the moment it has broken links (Edit: I have tracked down one instance of his code in a forum here - How to prevent TabControl from doing multi rows?), so here is the xaml for the TabControl (does not require any further code):

<TabControl x:Name="TabControl2" Height="Auto" TabStripPlacement="Bottom" VerticalAlignment="Bottom" Template="{DynamicResource TabControlControlTemplate1}" IsSynchronizedWithCurrentItem="True">
  <TabControl.Resources>
    <Style x:Key="TabScrollerRepeatButtonStyle" TargetType="{x:Type RepeatButton}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate>
            <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1" Margin="1,0">
              <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding ContentControl.Content}"/>
            </Border>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

    <ControlTemplate x:Key="TabControlControlTemplate1" TargetType="{x:Type TabControl}">
      <Grid x:Name="Grid" KeyboardNavigation.TabNavigation="Local">
        <Grid.ColumnDefinitions>
          <ColumnDefinition x:Name="ColumnDefinition0"/>
          <ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition x:Name="RowDefinition0" Height="Auto"/>
          <RowDefinition x:Name="RowDefinition1" Height="*"/>
        </Grid.RowDefinitions>
        <Border Grid.Row="1" Grid.Column="0" x:Name="ContentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0,1,1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.DirectionalNavigation="Contained">
          <Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
            <Border Background="{TemplateBinding Background}" x:Name="Border1">
              <ContentPresenter DataContext="{x:Null}" Margin="{TemplateBinding Padding}" x:Name="PART_SelectedContentHost" Content="{TemplateBinding SelectedContent}" ContentTemplate="{TemplateBinding SelectedContentTemplate}" ContentTemplateSelector="{TemplateBinding SelectedContentTemplateSelector}" ContentSource="SelectedContent"/>
            </Border>
          </Border>
        </Border>
        <ScrollViewer x:Name="HeaderPanel" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="0,0,0,0" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
          <ScrollViewer.Style>
            <Style TargetType="{x:Type ScrollViewer}">
              <Setter Property="Template">
                <Setter.Value>
                  <ControlTemplate>
                    <Grid Margin="0,0,0,0" Grid.Row="0" Grid.Column="0" x:Name="HeaderPanel">
                      <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="20"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="20"/>
                      </Grid.ColumnDefinitions>
                      <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                      </Grid.RowDefinitions>
                      <RepeatButton Grid.Column="1" Content="&lt;" Command="ScrollBar.LineLeftCommand" Style="{DynamicResource TabScrollerRepeatButtonStyle}" Visibility="{Binding Path=ComputedHorizontalScrollBarVisibility, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
                      <ScrollContentPresenter Grid.Column="2" Content="{TemplateBinding ScrollViewer.Content}" />
                      <RepeatButton Grid.Column="3" Content="&gt;" Command="ScrollBar.LineRightCommand" Style="{DynamicResource TabScrollerRepeatButtonStyle}" Visibility="{Binding Path=ComputedHorizontalScrollBarVisibility, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>
                    </Grid>
                  </ControlTemplate>
                </Setter.Value>
              </Setter>
            </Style>
          </ScrollViewer.Style>
          <StackPanel IsItemsHost="true" Orientation="Horizontal" Background="{x:Null}" KeyboardNavigation.TabIndex="1"/>
        </ScrollViewer>
      </Grid>
    </ControlTemplate>
  </TabControl.Resources>
  <TabItem x:Name="TabItem1" Header="TabItem1"/>
  <TabItem x:Name="TabItem2" Header="TabItem2"/>
</TabControl>

How might I adjust the appearance of each TabItem? For instance, I would like to place a TextBox and TextBlock inside each TabItem, with the help of a StackPanel, so that I can have renameable tabs (collapsing one or the other as appropriate). I might also want to add a close button on each tab. Ordinarily, I would use something like the following:

                <TabControl.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Name="panel" Orientation="Horizontal">
                            <TextBox   Name="editHeader" Text="{Binding Header}" MaxWidth="250"/>
                            <TextBlock Name="textHeader" Text="{Binding Header}" />
                        </StackPanel>
                    </DataTemplate>
                </TabControl.ItemTemplate>

.. but this is having no effect at all. Any ideas would be appreciated. Thanks.

Edit: I am still trying to figure this out. Is it possible that the solution could involve ContentPresenter and/or SelectedContentTemplate?

Edit 2: (this doesn't add value to my question) I really, really wish WPF included something of this sort out of the box. I am baffled by TabControl's default behavior, and by the fact that there is no scrollable TabControl (nor simple solution for attaining one) after some years.

+1  A: 

Hey guesser. I've done something similar, I based mine off of this series though

http://www.blogs.intuidev.com/post/2010/post/2010/01/25/TabControlStyling_PartOne.aspx

The method used is to simply style the tabitems template. eg:

<TabControl.Resources>
    <Style TargetType="{x:Type TabItem}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabItem}">
                <!-- your custom template goes here -->

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</TabControl.Resources>

Unforunately this method means you will have to completely redefine the way the tab items look and behave. But the provided link gives good examples on how achieve this.

Hope it helps.

Val
I haven't yet gotten the TabItems bound the the Header (using Text="{Binding Header}" -- it's not working for whatever reason), but it is having a visible effect on the styling of the TabItems, so this might be a promising angle. Any idea why the Binding to Header would fail?
guesser
I now understand what you mean when you say that I'll have to redefine the way the tabitems look (ie. when I put just a TextBlock in there, I only get a TextBlock and no containing box). I'm hoping to avoid having to write that and behavior changes if at all possible.
guesser
Not sure why the binding would fail, but try adding the binding to a setter above the template setter. "<Setter Property="Header" Value="{Binding ...}"/> And see if that works. There may be a better way, but at least now you have a fallback ;)
Val
+1  A: 

Just a thought,

have you tried just adding the text block to the TabItem.Header? If you do this instead of a template it might work. Has for me in the past

TerrorAustralis
This is an idea, and I played with this a bit before. I couldn't find a way to do it without always explicitly setting the TextBox and TextBlock Text at the appropriate time.. but I'd be very interested if there's a way to use this idea without having to do so. (e.g. some way to do it in the xaml, with a binding to a value)
guesser
Do you have some object sitting behind the tabs that you could bind to? for instance, a list of string or something? cos if you try to bind to the header when you are using the header property it will stuff up. As an aside, there is a way to use binding between sibing elements. Just name the element (for instance, `Name = "MyControl"` then you can bind to it using `Binding ElementName=MyControl, Path=PropertyToBindTo`. Failing that you can use `Binding RelativeSource={RelativeSource, Mode=FindAncestor, AncestorType=TypeOfParentYouWantToBindTo}`
TerrorAustralis
Thanks. That info about binding between siblings might come in handy for dealing with the visibility. At the moment, I have an invisible ToggleButton in the TabItem's ContentPresenter (a modification upon my solution to this question) and I'm binding visibility to its IsChecked property using your tip. I'm not sure if I'm going to be able to access it from my code, but it's worth a shot.
guesser
Depends how you want the text box to become editable. You could just use DataTriggers so when you select the tab the header becomes editable, just use a datatrigger bound to the IsSelected property (or whatever it is in tab templates). If you want tho, im willing to correspond with you on the subject, cos if you are adding that much logic into your UI, a viewmodel would probably be the way to go here :)
TerrorAustralis
I've removed the invisible ToggleButton, and have now set up my TabControl's ItemsSource to be bound to a list of objects. The objects contain an extra value reflecting whether or not editing is occurring, and I'm now binding visibility to that. I'm completely unfamiliar with viewmodels. Corresponding may be a good idea :)
guesser
kk, if you want you can reach me at [email protected] I have a few samples of ViewModel usage which would suit your situation perfectly
TerrorAustralis
A: 

I think I've got it (though still crossing fingers a bit -- I haven't yet dealt with Visibility of the TextBlock vs TextBox for renaming).

It is similar to Val's solution in that I'm working in TabControl.Resources on its TabItem, but the Property concerned is HeaderTemplate and I just override the ContentPresenter in a DataTemplate. (measures to avoid replacing\destroying a lot of good behavior that comes for free with the TabControl)

<TabControl.Resources>
    <Style TargetType="{x:Type TabItem}">
        <Setter Property="HeaderTemplate">
            <Setter.Value>
                <DataTemplate>
                    <ContentPresenter>
                        <ContentPresenter.Content>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="{TemplateBinding Content}"/>
                                <TextBox Text="{TemplateBinding Content}"/>
                            </StackPanel>
                        </ContentPresenter.Content>
                    </ContentPresenter>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</TabControl.Resources>

I'm somewhat of a WPF newb, so this is basically a result of persistent experimentation with what I could find Googling. For those interested, this link (on StackOverflow) helped me most in the end - WPF TabItem Header Styling

guesser