NeoIntelligence IT Solutions

company development web blog

NeoTab – Animated Tab Control in WPF

Fortunately, I found some time to start blogging about my final year project. I’m starting with a fancy WPF TabControl I’ve used in my application. Here is the final result of the control.

Sweet, isn’t it ? Let’s demystify it !

As you can see from the screenshot of the application below, there are 3 main areas in my application layout first of which is the logo, second one is where the most of the things take place, The NeoTab ! That’s a custom animated tab control with its unique look and own routed events. 3rd Area is the Profile Bar and it features the selected Patients’ quick information.

Layout of NeoMobile

I will get back to animations you saw on the captured video later in this series but now here how I created the tab.

The NeoTab WPF Control

Tabs are what we always use in web browsers recently. Although there were many solutions available online, the results weren’t  satisfactory for my case. This one was quite inspiring. Thanks to blogger.

NeoTab Class

I started creating a new custom control deriving from TabControl that implements this behaviour which I named it as NeoTab.

using System;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Threading;
using NeoFramework.Domain;

namespace NeoMobile.Controls
{
    //
    public class NeoTab : TabControl
    {
        private DispatcherTimer timer;

        // this event will be fired when the user clicks to
        // change the index of the tab control
        public static readonly RoutedEvent SelectionChangingEvent = EventManager.RegisterRoutedEvent(
            "SelectionChanging", RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(NeoTab));

        public event RoutedEventHandler SelectionChanging
        {
            add { AddHandler(SelectionChangingEvent, value); }
            remove { RemoveHandler(SelectionChangingEvent, value); }
        }

        public NeoTab()
        {
            DefaultStyleKey = typeof(NeoTab);
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            // I've put this code here to check if the user has
            // selected a patient before trying to change the index
            // for the case that the user has not choose any patients from the list
            // the Dispatcher will not invoke because we need the Patient
            // info for the other tabs to record !!
            Patient p = ApplicationHelper.GetProperty<Patient>("SelectedPatient");
            if (p == null)
                return;

            this.Dispatcher.BeginInvoke(
                (Action)delegate
                {
                    this.RaiseSelectionChangingEvent();

                    this.StopTimer();

                    this.timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 500) };

                    EventHandler handler = null;
                    handler = (sender, args) =>
                    {
                        this.StopTimer();
                        base.OnSelectionChanged(e);
                    };
                    this.timer.Tick += handler;
                    this.timer.Start();
                });
        }

        // This method raises the event to change the tab items
        private void RaiseSelectionChangingEvent()
        {
            var args = new RoutedEventArgs(SelectionChangingEvent);
            RaiseEvent(args);
        }

        private void StopTimer()
        {
            if (this.timer != null)
            {
                this.timer.Stop();
                this.timer = null;
            }
        }
    }
}

The Styles

I put a Grid inside the TabItem control and there is a border defined inside the tabitem to emphasize menu items a bit more. I have 2 triggers for the tabitem when the SelectionChangingEvent occurs. The opacity of the content becomes 0.5 when IsSelected property is false. It becomes fully visible again in a second after the animation.

You might change the color brushes for your own controls.
TabItem Styles

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style x:Key="NeoTabStyle" TargetType="{x:Type TabItem}">
        <Setter Property="FontSize" Value="14"/>
        <Setter Property="Header"  />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type TabItem}">
                    <Grid Width="Auto" Height="Auto" x:Name="TabItemRoot" Margin="10,0,10,0">
                        <ContentPresenter Margin="13,5,13,5" x:Name="Content" ContentSource="Header" RecognizesAccessKey="True"/>
                        <Border x:Name="border" Height="Auto" Padding="0,0,0,0" Opacity="0" Background="BorderBackgroundBrush">
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsSelected" Value="true">
                            <Setter TargetName="border" Property="Opacity" Value="1"/>
                            <Setter TargetName="Content" Property="Opacity" Value="1"/>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="false">
                            <Setter TargetName="Content" Property="Opacity" Value="0.5"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

TabControl Styles

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:Controls="clr-namespace:NeoMobile.Controls"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <Style TargetType="Controls:NeoTab">
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="Padding" Value="4,4,4,4"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="BorderBrush" Value="{StaticResource TabControlNormalBorderBrush}"/>
        <Setter Property="Background" Value="#F9F9F9"/>
        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        <Setter Property="VerticalContentAlignment" Value="Stretch"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Controls:NeoTab">

                    <Grid ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="*"/>
                        </Grid.RowDefinitions>

                        <Border Height="30" Margin="10,0,10,10"  HorizontalAlignment="Left" VerticalAlignment="Top" Width="Auto" Grid.Row="1" CornerRadius="12,12,12,12" Background="{TemplateBinding Background}" BorderThickness="2,2,2,2" BorderBrush="#FFFFFFFF">
                            <TabPanel x:Name="HeaderPanel" HorizontalAlignment="Center" VerticalAlignment="Center" IsItemsHost="true" Grid.Column="0" Grid.Row="0" KeyboardNavigation.TabIndex="1"/>
                        </Border>

                        <Grid Grid.Row="1" Margin="0,40,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                            <Viewport3D x:Name="vp3D" Visibility="Hidden" Width="Auto" Height="Auto">
                                <Viewport3D.Camera>
                                    <PerspectiveCamera x:Name="camera" Position="0,0,0.5" LookDirection="0,0,-1" FieldOfView="90" />
                                </Viewport3D.Camera>
                                <Viewport3D.Children>
                                    <ModelVisual3D>
                                        <ModelVisual3D.Content>
                                            <Model3DGroup>
                                                <DirectionalLight Color="#444" Direction="0,0,-1" />
                                                <AmbientLight Color="#BBB" />
                                            </Model3DGroup>
                                        </ModelVisual3D.Content>
                                    </ModelVisual3D>
                                    <ModelVisual3D>
                                        <ModelVisual3D.Content>
                                            <GeometryModel3D>
                                                <GeometryModel3D.Geometry>
                                                    <MeshGeometry3D  TriangleIndices="0,1,2 2,3,0" TextureCoordinates="0,1 1,1 1,0 0,0" Positions="-0.5,-0.5,0 0.5,-0.5,0 0.5,0.5,0 -0.5,0.5,0" />
                                                </GeometryModel3D.Geometry>
                                                <GeometryModel3D.Material>
                                                    <DiffuseMaterial>
                                                        <DiffuseMaterial.Brush>
                                                            <VisualBrush Visual="{Binding ElementName=BorderIn}" Stretch="Uniform" />
                                                        </DiffuseMaterial.Brush>
                                                    </DiffuseMaterial>
                                                </GeometryModel3D.Material>
                                                <GeometryModel3D.BackMaterial>
                                                    <DiffuseMaterial>
                                                        <DiffuseMaterial.Brush>
                                                            <VisualBrush Visual="{Binding ElementName=BorderIn}" Stretch="Uniform">
                                                                <VisualBrush.RelativeTransform>
                                                                    <ScaleTransform ScaleX="-1" CenterX="0.5" />
                                                                </VisualBrush.RelativeTransform>
                                                            </VisualBrush>
                                                        </DiffuseMaterial.Brush>
                                                    </DiffuseMaterial>
                                                </GeometryModel3D.BackMaterial>
                                                <GeometryModel3D.Transform>
                                                    <RotateTransform3D>
                                                        <RotateTransform3D.Rotation>
                                                            <AxisAngleRotation3D x:Name="rotate" Axis="0,3,0" Angle="0" />
                                                        </RotateTransform3D.Rotation>
                                                    </RotateTransform3D>
                                                </GeometryModel3D.Transform>
                                            </GeometryModel3D>
                                        </ModelVisual3D.Content>
                                    </ModelVisual3D>
                                </Viewport3D.Children>
                            </Viewport3D>
                            <Border x:Name="BorderOut" VerticalAlignment="Stretch">
                                <Border x:Name="BorderIn" VerticalAlignment="Stretch" Background="#00000000" >
                                    <Grid>
                                        <Controls:RoundedBox Margin="10" />
                                        <ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="20"/>
                                    </Grid>
                                </Border>
                            </Border>
                        </Grid>
                    </Grid>

                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="Controls:NeoTab.SelectionChanging">
                            <BeginStoryboard>
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="vp3D"  Storyboard.TargetProperty="Visibility">
                                        <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{x:Static Visibility.Visible}" />
                                        <DiscreteObjectKeyFrame KeyTime="0:0:1.1" Value="{x:Static Visibility.Hidden}" />
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimation To="0" Duration="0:0:0.05" Storyboard.TargetName="BorderOut" Storyboard.TargetProperty="Opacity" />
                                    <DoubleAnimation BeginTime="0:0:1.05" Duration="0:0:0.05" To="1" Storyboard.TargetName="BorderOut" Storyboard.TargetProperty="Opacity" />
                                    <Point3DAnimation To="0,0,1.1" From="0,0,0.5"  BeginTime="0:0:0.05" Duration="0:0:0.5" AutoReverse="True" DecelerationRatio="0.3"  Storyboard.TargetName="camera"
                                            Storyboard.TargetProperty="(PerspectiveCamera.Position)" />
                                    <DoubleAnimation From="0" To="180" AccelerationRatio="0.3" DecelerationRatio="0.3" BeginTime="0:0:0.05" Duration="0:0:1"  Storyboard.TargetName="rotate" Storyboard.TargetProperty="Angle" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>

            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

For now, I have nothing more to add. I will maybe return back to this topic and add some more explanations after my final year project presentation. The full source code will be also available here. I hope someone will benefit from this article.

Any feedback would be greatly appreciated!

Have Fun !

2 responses to “NeoTab – Animated Tab Control in WPF

  1. Huseyin Kombayci January 14, 2011 at 4:01 pm

    Kardesim projen bitmis, hayirlisi olsun. Video ile codelari inceledim. Super olmus. Emegine saglik.

Leave a comment