Silverlight tabcontrol with scrollable tabItems

by dsoltesz 20. July 2010 18:31 >

I ran into an issue where I had more tab items than could fit within the desired width of my tab control.  Instead of all the tab items appearing on a single line, they would wrap and create a multiline affect which I did not want.  I wanted the user to be able to scroll through the tab items if needed.  I did some checking to see if someone had built or customized the silverlight toolkit to support this but I did not find anything, so I decided to create a custom tabcontrol with scrollable items.  Below is a picture of the default tab control and my custom scrollable tabcontrol.  Note: I have only modified the "Top" layout for the tabcontrol so if someone wants to use one of the other layouts, they will have to update accordingly.

 

Here is the code for the custom scrollable tab control.

file: ScrollableTab.xaml.cs


 [StyleTypedProperty(Property = "TabLeftButtonTopStyle", StyleTargetType = typeof(RepeatButton)),
     StyleTypedProperty(Property = "TabRightButtonTopStyle", StyleTargetType = typeof(RepeatButton)),
     TemplatePart(Name = "TabLeftButtonTop", Type = typeof(RepeatButton)),
	 TemplatePart(Name = "TabRightButtonTop", Type = typeof(RepeatButton)),
     TemplatePart(Name = "TabScrollViewerTop", Type = typeof(ScrollViewer)),
     TemplatePart(Name = "TabPanelTop", Type = typeof(TabPanel))]
    public class SlidingTabControl : System.Windows.Controls.TabControl
    {
		private RepeatButton tabLeftButton;
		private RepeatButton tabRightButton;
        private ScrollViewer tabScrollViewer;
        private TabPanel tabPanelTop;
        private double currentHorizontalOffset = 0;
        private double maxHorizontalOffset = 0;

        /// 
        /// Tab Top Left Button style
        /// 
        public static readonly DependencyProperty TabLeftButtonTopStyleProperty = DependencyProperty.Register(
            "TabLeftButtonTopStyle",
            typeof(Style),
            typeof(SlidingTabControl),
            new PropertyMetadata(null));

        /// 
        /// Tab Top Left Button style
        /// 
        public static readonly DependencyProperty TabRightButtonTopStyleProperty = DependencyProperty.Register(
            "TabRightButtonTopStyle",
            typeof(Style),
            typeof(SlidingTabControl),
            new PropertyMetadata(null));

        /// 
        /// Initializes a new instance of the  class.
        /// 
        public SlidingTabControl()
        {
            this.DefaultStyleKey = typeof(SlidingTabControl);
            this.SelectionChanged += new SelectionChangedEventHandler(SlidingTabControl_SelectionChanged);
        }

        /// 
        /// Gets or sets the Tab Top Left Button style.
        /// 
        /// The left button style style.
        [Description("Gets or sets the tab top left button style")]
        [Category("ScrollButton")]
        public Style TabLeftButtonTopStyle
        {
            get { return (Style)GetValue(TabLeftButtonTopStyleProperty); }
            set { SetValue(TabLeftButtonTopStyleProperty, value); }
        }

        /// 
        /// Gets or sets the Tab Top Right Button style.
        /// 
        /// The left button style style.
        [Description("Gets or sets the tab top right button style")]
        [Category("ScrollButton")]
        public Style TabRightButtonTopStyle
        {
            get { return (Style)GetValue(TabRightButtonTopStyleProperty); }
            set { SetValue(TabRightButtonTopStyleProperty, value); }
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            //remove old handlers
            if (null != this.tabLeftButton)
            {
                this.tabLeftButton.Click -= this.tabLeftButton_Click;
            }
            if (null != this.tabRightButton)
            {
                this.tabRightButton.Click -= this.tabRightButton_Click;
            }

            //add new handlers
            this.tabLeftButton = GetTemplateChild("TabLeftButtonTop") as RepeatButton;
            this.tabRightButton = GetTemplateChild("TabRightButtonTop") as RepeatButton;
            this.tabScrollViewer = GetTemplateChild("TabScrollViewerTop") as ScrollViewer;
            this.tabPanelTop = GetTemplateChild("TabPanelTop") as TabPanel;
            if (null != this.tabLeftButton)
            {
                this.tabLeftButton.Click += new RoutedEventHandler(tabLeftButton_Click);
            }
            if (null != this.tabRightButton)
            {
                this.tabRightButton.Click += new RoutedEventHandler(tabRightButton_Click);
            }

        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            var size = base.ArrangeOverride(finalSize);

            if (this.tabPanelTop != null && this.tabScrollViewer != null)
            {
                if (this.tabPanelTop.ActualWidth > this.tabScrollViewer.ActualWidth)
                {
                    maxHorizontalOffset = this.tabPanelTop.ActualWidth - this.tabScrollViewer.ActualWidth + 30;
                }
                SetScrollButtonVisibility();
            }

            return size;
        }
        private void SlidingTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            SlidingTabControl tabControl = sender as SlidingTabControl;
            tabControl.Dispatcher.BeginInvoke(new Action(() => UpdateZIndex(sender as SlidingTabControl)));
        }
        private void tabRightButton_Click(object sender, RoutedEventArgs e)
        {
            if (null != this.tabScrollViewer && null != this.tabPanelTop)
            {
                currentHorizontalOffset = this.tabScrollViewer.HorizontalOffset + 25;
                if (currentHorizontalOffset > maxHorizontalOffset)
                    currentHorizontalOffset = maxHorizontalOffset;
                this.tabScrollViewer.ScrollToHorizontalOffset(currentHorizontalOffset);
                SetScrollButtonVisibility();
            }
        }
        private void tabLeftButton_Click(object sender, RoutedEventArgs e)
        {
            if (null != this.tabScrollViewer)
            {
                
                currentHorizontalOffset = this.tabScrollViewer.HorizontalOffset - 25;
                if (currentHorizontalOffset < 0)
                    currentHorizontalOffset = 0;
                
                this.tabScrollViewer.ScrollToHorizontalOffset(currentHorizontalOffset);
                SetScrollButtonVisibility();
            }
            
        }
        private void SetScrollButtonVisibility()
        {
            if (null != this.tabScrollViewer)
            {
                if (null != this.tabLeftButton)
                {
                    if (this.currentHorizontalOffset > 0)
                        this.tabLeftButton.Visibility = Visibility.Visible;
                    else
                        this.tabLeftButton.Visibility = Visibility.Collapsed;

                }
                if (null != this.tabRightButton)
                {
                    if (currentHorizontalOffset >= maxHorizontalOffset)
                    {
                        this.tabRightButton.Visibility = System.Windows.Visibility.Collapsed;
                    }
                    else
                    {
                        this.tabRightButton.Visibility = System.Windows.Visibility.Visible;
                    }
                }
            }
        }
        private void UpdateZIndex(SlidingTabControl tc)
        {
            if (tc != null)
            {
                foreach (TabItem tabItem in tc.Items)
                {
                    tabItem.SetValue(Canvas.ZIndexProperty, (tabItem == tc.SelectedItem ? tc.Items.Count : (tc.Items.Count - 1) - tc.Items.IndexOf(tabItem)));
                }
            }

        }

        
    }

 

file generic.xaml

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TabControl"
    xmlns:System_Windows_Controls_Primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"
    >

 

    <Style TargetType="local:SlidingTabControl">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Background">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFFFFFFF" Offset="0"/>
                    <GradientStop Color="#FFFEFEFE" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFA3AEB9" Offset="0"/>
                    <GradientStop Color="#FF8399A9" Offset="0.375"/>
                    <GradientStop Color="#FF718597" Offset="0.375"/>
                    <GradientStop Color="#FF617584" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="Padding" Value="5"/>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:SlidingTabControl">
                    <Grid>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition GeneratedDuration="0"/>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualTop">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualBottom">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualLeft">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualRight">
                                            <SplineDoubleKeyFrame KeyTime="0" Value="1"/>
                                        </DoubleAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid x:Name="TemplateTop" Visibility="Collapsed">
                         <Grid.ColumnDefinitions>
                          <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
       </Grid.ColumnDefinitions>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
       <RepeatButton Content="&lt;" x:Name="TabLeftButtonTop" Style="{TemplateBinding TabLeftButtonTopStyle}"/>
                            <ScrollViewer x:Name="TabScrollViewerTop" Grid.Column="1" IsTabStop="False" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Hidden" >
                         <System_Windows_Controls_Primitives:TabPanel x:Name="TabPanelTop" Margin="20,2,2,-1" Canvas.ZIndex="1"/>
       </ScrollViewer>
       <RepeatButton Content="&gt;" x:Name="TabRightButtonTop" Style="{TemplateBinding TabRightButtonTopStyle}" Grid.Column="2"/>
                         <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="0,0,3,3" MinWidth="10" MinHeight="10" Grid.Row="1" Grid.ColumnSpan="3">
                          <ContentPresenter x:Name="ContentTop" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                         </Border>
                         <Border x:Name="DisabledVisualTop" Background="#8CFFFFFF" CornerRadius="0,0,3,3" IsHitTestVisible="False" Opacity="0" Grid.Row="1" Grid.RowSpan="2" Canvas.ZIndex="1"/>
                        </Grid>
                        <Grid x:Name="TemplateBottom" Visibility="Collapsed">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <System_Windows_Controls_Primitives:TabPanel x:Name="TabPanelBottom" Margin="2,-1,2,2" Grid.Row="1" Canvas.ZIndex="1"/>
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3,3,0,0" MinWidth="10" MinHeight="10">
                                <ContentPresenter x:Name="ContentBottom" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                            </Border>
                            <Border x:Name="DisabledVisualBottom" Background="#8CFFFFFF" CornerRadius="3,3,0,0" IsHitTestVisible="False" Opacity="0" Canvas.ZIndex="1"/>
                        </Grid>
                        <Grid x:Name="TemplateLeft" Visibility="Collapsed">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto"/>
                                <ColumnDefinition Width="*"/>
                            </Grid.ColumnDefinitions>
                            <System_Windows_Controls_Primitives:TabPanel x:Name="TabPanelLeft" Margin="2,2,-1,2" Canvas.ZIndex="1"/>
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" CornerRadius="0,3,3,0" MinWidth="10" MinHeight="10">
                                <ContentPresenter x:Name="ContentLeft" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                            </Border>
                            <Border x:Name="DisabledVisualLeft" Background="#8CFFFFFF" Grid.Column="1" CornerRadius="0,3,3,0" IsHitTestVisible="False" Opacity="0" Canvas.ZIndex="1"/>
                        </Grid>
                        <Grid x:Name="TemplateRight" Visibility="Collapsed">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="*"/>
                                <ColumnDefinition Width="Auto"/>
                            </Grid.ColumnDefinitions>
                            <System_Windows_Controls_Primitives:TabPanel x:Name="TabPanelRight" Grid.Column="1" Margin="-1,2,2,2" Canvas.ZIndex="1"/>
                            <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3,0,0,3" MinWidth="10" MinHeight="10">
                                <ContentPresenter x:Name="ContentRight" Cursor="{TemplateBinding Cursor}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalAlignment}"/>
                            </Border>
                            <Border x:Name="DisabledVisualRight" Background="#8CFFFFFF" CornerRadius="3,0,0,3" IsHitTestVisible="False" Margin="0" Opacity="0" Canvas.ZIndex="1"/>
                        </Grid>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

  
</ResourceDictionary>

 

Download code

SlidingTabControlDemo.zip (2.10 mb)

Tags: ,

c# | wcf ria services

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading

about the author

My name is Dan Soltesz, and I"m a consultant specializing in Microsoft technologies

  • Silverlight 2.0,3.0,4.0,5.0
  • Windows Phone 7,8
  • Windows 8
  • WPF
  • MVC / SPA
  • ASP.NET 2.0,3.0,3.5,4.0
  • WCF, WCF Ria Services
  • Entity Framework / EF Code First
  • C#
  • MVVM, Repository Pattern

to name a few.  On this site you will find links to sites/blogs that I read and my own attempt at writing some blogs that may help others as well as a few miscellaneous items.  If there is something that you need to know about me and cannot find on this site, please feel free to contact me.

page list

sponsors

month list

recentcomments

Comment RSS