5

I have a TabControl where each TabItem has a separate control as its Content element. Now, I can easily execute a storyboard when switching to a tab by using a UserControl.Loaded EventTrigger. However, I also want to run an exit animation when switching from one tab to another (i.e. allow the old Content control to animate away, followed by the new Content control's entrance animation).

Is it possible to do this with standard WPF constructs?

If not, how would I go about developing a custom solution that handles this?

Edit: I went ahead and made a modified TabControl that extends the base TabControl and overrode its OnSelectionChanged method as follows:

protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
    if (e.AddedItems.Count == 1 && e.RemovedItems.Count == 1)
    {
        var oldTab = e.RemovedItems[0] as TabItem;

        if (oldTab != null)
        {
            var exitStoryboard = /** code to find the storyboard **/
            if (exitStoryboard != null)
            {
                exitStoryboard.Completed = (_, __) => base.OnSelectionChanged(e);
                exitStoryboard.Begin();
                return;
            }
        }
    }
    base.OnSelectionChanged(e);
}

This works, except when I click between the tabs too quickly in which case the base.OnSelectionChanged never gets called, presumably because the storyboard is not active any more. Tips?

sohum
  • 3,207
  • 2
  • 39
  • 63

3 Answers3

1

Here's a solution for 2 tabs, the general idea is to pop up an image of the last tab after the selection changes, and then fade the current tab in.

With a little effort you could probably genericize it for any number of tabs by tracking the last tab for the VisualBrush in a property instead of the hardcoded "other" tab used here.

<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <TabControl>
      <TabItem x:Name="tab1" Header="Tab 1">
         <Border Background="Transparent">
            <Grid>
               <TextBlock FontSize="40" Foreground="Red" Text="Tab 1 Contents">
                  <TextBlock.Triggers>
                     <EventTrigger RoutedEvent="Loaded">
                        <BeginStoryboard>
                           <Storyboard>
                              <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
                                 <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0"/>
                              </DoubleAnimationUsingKeyFrames>
                              <DoubleAnimation
                                 Duration="0:0:0.25"
                                 From="1"
                                 Storyboard.TargetName="tab2Shadow"
                                 Storyboard.TargetProperty="Opacity"
                                 To="0"/>
                              <DoubleAnimation
                                 BeginTime="0:0:0.25"
                                 Duration="0:0:0.25"
                                 From="0"
                                 Storyboard.TargetProperty="Opacity"
                                 To="1"/>
                           </Storyboard>
                        </BeginStoryboard>
                     </EventTrigger>
                  </TextBlock.Triggers>
               </TextBlock>
               <Rectangle x:Name="tab2Shadow">
                  <Rectangle.Fill>
                     <VisualBrush
                        AlignmentX="Left"
                        AlignmentY="Top"
                        AutoLayoutContent="False"
                        Stretch="None"
                        Visual="{Binding ElementName=tab2, Path=Content}"/>
                  </Rectangle.Fill>
               </Rectangle>
            </Grid>
         </Border>
      </TabItem>
      <TabItem x:Name="tab2" Header="Tab 2">
         <Border Background="Transparent">
            <Grid>
               <TextBlock FontSize="40" Foreground="Red" Text="Tab 2 Contents">
                  <TextBlock.Triggers>
                     <EventTrigger RoutedEvent="Loaded">
                        <BeginStoryboard>
                           <Storyboard>
                              <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity">
                                 <DiscreteDoubleKeyFrame KeyTime="0:0:0" Value="0.0"/>
                              </DoubleAnimationUsingKeyFrames>
                              <DoubleAnimation
                                 Duration="0:0:0.25"
                                 From="1"
                                 Storyboard.TargetName="tab1Shadow"
                                 Storyboard.TargetProperty="Opacity"
                                 To="0"/>
                              <DoubleAnimation
                                 BeginTime="0:0:0.25"
                                 Duration="0:0:0.25"
                                 From="0"
                                 Storyboard.TargetProperty="Opacity"
                                 To="1"/>
                           </Storyboard>
                        </BeginStoryboard>
                     </EventTrigger>
                  </TextBlock.Triggers>
               </TextBlock>
               <Rectangle x:Name="tab1Shadow">
                  <Rectangle.Fill>
                     <VisualBrush
                        AlignmentX="Left"
                        AlignmentY="Top"
                        AutoLayoutContent="False"
                        Stretch="None"
                        Visual="{Binding ElementName=tab1, Path=Content}"/>
                  </Rectangle.Fill>
               </Rectangle>
            </Grid>
         </Border>
      </TabItem>
   </TabControl>
</Grid>
Robert Macnee
  • 11,650
  • 4
  • 40
  • 35
  • This looks promising although I suppose I could only do a basic animation that doesn't know anything about the XAML elements, unless I wanted to track those in separate visual brushes? – sohum Aug 28 '12 at 14:20
  • If an animation plays on the old tab when selection leaves it, the VisualBrush will display the animated controls on the old tab, so you could do something with them instead of the simple fade effect above. – Robert Macnee Aug 28 '12 at 14:36
0

You can use the TabControl.SelectionChanged event.

franssu
  • 2,422
  • 1
  • 20
  • 29
0

You can define a custom style for your tabcontrol and do animation on Selector.SelectionChanged RoutedEvent

        <Style x:Key="{x:Type TabControl}"
           TargetType="TabControl">
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="TabControl">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="40" />
                            <RowDefinition />
                        </Grid.RowDefinitions>

                        <UniformGrid Grid.Row="0"
                                     Rows="1"
                                     IsItemsHost="True" />
                        <ContentPresenter x:Name="TabContent"
                                          Grid.Row="2"
                                          Content="{TemplateBinding SelectedContent}">

                        </ContentPresenter>
                    </Grid>

                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="Selector.SelectionChanged">
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation From="0"
                                                     To="1"
                                                     Storyboard.TargetProperty="Opacity"
                                                     Storyboard.TargetName="TabContent"
                                                     Duration="0:0:0.5" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
Suresh
  • 4,091
  • 28
  • 35