locked
WPF TabControl Sliding ContentControl RRS feed

  • Question

  • Hello, I need a little guidance.
    I created a SlidingContentControl, which I implemented in TabControl. The problem is that the animation is not working. Do you know where my mistake. Thanks a lot

    public enum SlideDirection
        {
            LeftToRight = 1,
            RightToLeft = 2,
            UpToDown = 3,
            DownToUp = 4
        }

     public class SlidingContentControl : ContentControl
        {
            #region Variables
            private ContentPresenter mainContent;
            private Shape paintArea;
            #endregion
    
            #region Properties
            public SlideDirection SlideDirection
            {
                get { return (SlideDirection)GetValue(SlideDirectionProperty); }
                set { SetValue(SlideDirectionProperty, value); }
            }
    
            public double EasingAmplitude
            {
                get { return Convert.ToDouble(GetValue(EasingAmplitudeProperty)); }
                set { SetValue(EasingAmplitudeProperty, value); }
            }
    
            public double SlideDuration
            {
                get { return Convert.ToDouble(GetValue(SlideDurationProperty)); }
                set { SetValue(SlideDurationProperty, value); }
            }
    
            public double FadeOutDuration
            {
                get { return Convert.ToDouble(GetValue(FadeOutDurationProperty)); }
                set { SetValue(FadeOutDurationProperty, value); }
            }
    
            public double FadeInDuration
            {
                get { return Convert.ToDouble(GetValue(FadeInDurationProperty)); }
                set { SetValue(FadeInDurationProperty, value); }
            }
    
            public bool FadeOnSlide
            {
                get { return (bool)GetValue(FadeOnSlideProperty); }
                set { SetValue(FadeOnSlideProperty, value); }
            }
            #endregion
    
            #region Dependency Properties
            public static readonly DependencyProperty SlideDirectionProperty = DependencyProperty.Register("SlideDirection", typeof(SlideDirection), typeof(SlidingContentControl), new PropertyMetadata(SlideDirection.RightToLeft));
            public static readonly DependencyProperty EasingAmplitudeProperty = DependencyProperty.Register("EasingAmplitude", typeof(double), typeof(SlidingContentControl), new PropertyMetadata(0.5));
            public static readonly DependencyProperty SlideDurationProperty = DependencyProperty.Register("SlideDuration", typeof(double), typeof(SlidingContentControl), new PropertyMetadata(0.5));
            public static readonly DependencyProperty FadeOutDurationProperty = DependencyProperty.Register("FadeOutDuration", typeof(double), typeof(SlidingContentControl), new PropertyMetadata(0.3));
            public static readonly DependencyProperty FadeInDurationProperty = DependencyProperty.Register("FadeInDuration", typeof(double), typeof(SlidingContentControl), new PropertyMetadata(0.7));
            public static readonly DependencyProperty FadeOnSlideProperty = DependencyProperty.Register("FadeOnSlide", typeof(bool), typeof(SlidingContentControl), new PropertyMetadata(false));
            #endregion
    
            #region Construction
            static SlidingContentControl()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(SlidingContentControl), new FrameworkPropertyMetadata(typeof(SlidingContentControl)));
            }
            #endregion
    
            #region Public
            public override void OnApplyTemplate()
            {
                this.mainContent = (ContentPresenter)GetTemplateChild("PART_MainContent");
                this.paintArea = (Shape)GetTemplateChild("PART_PaintArea");
    
                base.OnApplyTemplate();
            }
            #endregion
    
            #region Protected
            protected override void OnContentChanged(object oldContent, object newContent)
            {
                try
                {
                    if (this.paintArea != null && this.mainContent != null)
                    {
                        this.paintArea.Fill = this.CreateBrushFromVisual(this.mainContent);
                        this.BeginAnimateContentReplacement();
                    }
                    base.OnContentChanged(oldContent, newContent);
    
                }
                catch (Exception)
                {
                }
            }
            #endregion
    
            #region Private
            /// <summary>
            /// Creates a brush based on the current appearance of a visual element. 
            /// The brush is an ImageBrush and once created, won't update its look
            /// </summary>
            /// <param name="visual">The visual element to take a snapshot of</param>
            private Brush CreateBrushFromVisual(Visual visual)
            {
                if (visual == null)
                {
                    throw new ArgumentNullException("visual");
                }
    
                var target = new RenderTargetBitmap(Convert.ToInt32(this.ActualWidth), Convert.ToInt32(this.ActualHeight), 96, 96, PixelFormats.Pbgra32);
                target.Render(visual);
                var brush = new ImageBrush(target);
                brush.Freeze();
                return brush;
            }
    
            /// <summary>
            /// Starts the animation for the new content
            /// </summary>
            private void BeginAnimateContentReplacement()
            {
                var newContentTransform = new TranslateTransform();
                var oldContentTransform = new TranslateTransform();
                this.paintArea.RenderTransform = oldContentTransform;
                this.mainContent.RenderTransform = newContentTransform;
                this.paintArea.Visibility = Visibility.Visible;
    
                switch (this.SlideDirection)
                {
                    case SlideDirection.LeftToRight:
                        newContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(-this.ActualWidth, 0));
                        oldContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(0, this.ActualWidth, (s, e) => this.paintArea.Visibility = Visibility.Hidden));
                        break;
                    case SlideDirection.RightToLeft:
                        newContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(this.ActualWidth, 0));
                        oldContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(0, -this.ActualWidth, (s, e) => this.paintArea.Visibility = Visibility.Hidden));
                        break;
                    case SlideDirection.UpToDown:
                        newContentTransform.BeginAnimation(TranslateTransform.YProperty, this.CreateSlideAnimation(-this.ActualHeight, 0));
                        oldContentTransform.BeginAnimation(TranslateTransform.YProperty, this.CreateSlideAnimation(0, this.ActualHeight, (s, e) => this.paintArea.Visibility = Visibility.Hidden));
                        break;
                    case SlideDirection.DownToUp:
                        newContentTransform.BeginAnimation(TranslateTransform.YProperty, this.CreateSlideAnimation(this.ActualHeight, 0));
                        oldContentTransform.BeginAnimation(TranslateTransform.YProperty, this.CreateSlideAnimation(0, -this.ActualHeight, (s, e) => this.paintArea.Visibility = Visibility.Hidden));
                        break;
                    default:
                        newContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(this.ActualWidth, 0));
                        oldContentTransform.BeginAnimation(TranslateTransform.XProperty, this.CreateSlideAnimation(0, -this.ActualWidth, (s, e) => this.paintArea.Visibility = Visibility.Hidden));
                        break;
                }
    
                if (this.FadeOnSlide)
                {
                    this.mainContent.BeginAnimation(OpacityProperty, this.CreateFadeAnimation(0, 1, this.FadeInDuration));
                    this.paintArea.BeginAnimation(OpacityProperty, this.CreateFadeAnimation(1, 0, this.FadeOutDuration));
                }
            }
    
            /// <summary>
            /// Creates the animation that moves content in or out of view.
            /// </summary>
            /// <param name="from">The starting value of the animation.</param>
            /// <param name="to">The end value of the animation.</param>
            /// <param name="whenDone">(optional)
            ///   A callback that will be called when the animation has completed.</param>
            private AnimationTimeline CreateSlideAnimation(double from, double to, EventHandler whenDone = null)
            {
    
                IEasingFunction ease = new BackEase
                {
                    Amplitude = this.EasingAmplitude,
                    EasingMode = EasingMode.EaseOut
                };
    
                var duration = new Duration(TimeSpan.FromSeconds(this.SlideDuration));
                var anim = new DoubleAnimation(from, to, duration) { EasingFunction = ease };
    
                if (whenDone != null) anim.Completed += whenDone;
    
                anim.Freeze();
    
                return anim;
            }
    
            private AnimationTimeline CreateFadeAnimation(double from, double to, double durationSeconds, EventHandler whenDone = null)
            {
                var duration = new Duration(TimeSpan.FromSeconds(durationSeconds));
                var anim = new DoubleAnimation(from, to, duration);
    
                if (whenDone != null)
                {
                    anim.Completed += whenDone;
                }
    
                anim.Freeze();
    
                return anim;
            }
            #endregion
        }
    <Style TargetType="{x:Type local:SlidingContentControl}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:SlidingContentControl}">
                        <Grid ClipToBounds="True">
                            <Rectangle x:Name="PART_PaintArea" Panel.ZIndex="0"/>
                            <ContentPresenter x:Name="PART_MainContent" Content="{TemplateBinding Content}" Panel.ZIndex="1"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

    <Style TargetType="{x:Type TabControl}">
            <Setter Property="Background" Value="{x:Null}" />
            <Setter Property="BorderBrush" Value="{x:Null}" />
            <Setter Property="ClipToBounds" Value="True" />
            <Setter Property="SnapsToDevicePixels" Value="True" />
            <Setter Property="UseLayoutRounding" Value="True"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TabControl}">
                        <Grid ClipToBounds="{TemplateBinding ClipToBounds}"
                              KeyboardNavigation.TabNavigation="Local"
                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <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>
                            <TabPanel x:Name="HeaderPanel"
                                      Grid.Row="0"
                                      Grid.Column="0"
                                      Panel.ZIndex="1"
                                      IsItemsHost="True"
                                      KeyboardNavigation.TabIndex="1" />
                            <Border x:Name="ContentPanel"
                                    Grid.Row="1"
                                    Grid.Column="0"
                                    Background="{TemplateBinding Background}"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}"
                                    KeyboardNavigation.DirectionalNavigation="Contained"
                                    KeyboardNavigation.TabIndex="2"
                                    KeyboardNavigation.TabNavigation="Local">
    
    
                                <controls:SlidingContentControl 
                                                                EasingAmplitude="0" 
                                                                SlideDirection="RightToLeft"
                                                                SlideDuration="0.5" 
                                                                FadeOutDuration="0.2" 
                                                                FadeInDuration="0.8" 
                                                                FadeOnSlide="True">
    
    
    
                                    <ContentPresenter x:Name="PART_SelectedContentHost"
                                                      UseLayoutRounding="False"
                                                      Margin="{TemplateBinding Padding}"
                                                      ContentSource="SelectedContent"
                                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
    
                                </controls:SlidingContentControl>
                              
    
    
                            </Border>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="TabStripPlacement" Value="Bottom">
                                <Setter TargetName="ContentPanel" Property="Grid.Row" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Grid.Row" Value="1" />
                                <Setter TargetName="HeaderPanel" Property="Margin" Value="2 0 2 2" />
                                <Setter TargetName="RowDefinition0" Property="Height" Value="*" />
                                <Setter TargetName="RowDefinition1" Property="Height" Value="Auto" />
                            </Trigger>
                            <Trigger Property="TabStripPlacement" Value="Left">
                            
                                <Setter TargetName="ColumnDefinition0" Property="Width" Value="Auto" />
                                <Setter TargetName="ColumnDefinition1" Property="Width" Value="*" />
                                <Setter TargetName="ContentPanel" Property="Grid.Column" Value="1" />
                                <Setter TargetName="ContentPanel" Property="Grid.Row" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Grid.Column" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Grid.Row" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Margin" Value="2 2 0 2" />
                                <Setter TargetName="RowDefinition0" Property="Height" Value="*" />
                                <Setter TargetName="RowDefinition1" Property="Height" Value="0" />
                            </Trigger>
                            <Trigger Property="TabStripPlacement" Value="Right">
                                <Setter TargetName="ColumnDefinition0" Property="Width" Value="*" />
                                <Setter TargetName="ColumnDefinition1" Property="Width" Value="Auto" />
                                <Setter TargetName="ContentPanel" Property="Grid.Column" Value="0" />
                                <Setter TargetName="ContentPanel" Property="Grid.Row" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Grid.Column" Value="1" />
                                <Setter TargetName="HeaderPanel" Property="Grid.Row" Value="0" />
                                <Setter TargetName="HeaderPanel" Property="Margin" Value="0 2 2 2" />
                                <Setter TargetName="RowDefinition0" Property="Height" Value="*" />
                                <Setter TargetName="RowDefinition1" Property="Height" Value="0" />
                            </Trigger>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>




    • Edited by ORRNY66 Thursday, September 29, 2016 5:25 PM
    Thursday, September 29, 2016 5:23 PM

Answers

  • I finally solved it. Just add 
    Content = "{TemplateBinding SelectedContent}"

    • Proposed as answer by Magnus (MM8)MVP Tuesday, October 4, 2016 5:12 PM
    • Marked as answer by ORRNY66 Wednesday, October 5, 2016 10:39 AM
    Tuesday, October 4, 2016 1:19 PM

All replies

  • Hi ORRNY66,

    Thank you for your post.

    >>"The problem is that the animation is not working. "

    Based on your code, the animation is start in OnContentChanged method. It mean the animation will not working until the content of SlidingContentControl is changed. If I place a SlidingContentControl in Window and change the content of this control, I can see the animation.

    slidingControl.Content = new Label { Content = "I am a label in sliding control" };

    In your example, TabControl will create multi SlidingControl instances for each tab item. When you click a tab header, it will switch SlidingControl instance instead of change the content of SlidingControl instance. SO the animation will not work.

    Best Regards,
    Li Wang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    • Proposed as answer by DotNet Wang Monday, October 3, 2016 9:15 AM
    Monday, October 3, 2016 9:15 AM
  • I finally solved it. Just add 
    Content = "{TemplateBinding SelectedContent}"

    • Proposed as answer by Magnus (MM8)MVP Tuesday, October 4, 2016 5:12 PM
    • Marked as answer by ORRNY66 Wednesday, October 5, 2016 10:39 AM
    Tuesday, October 4, 2016 1:19 PM
  • Hi ORRNY66,

    Thank you for sharing your solution. Please mark helpful replies as answer. Anyone who encountered similar issues will get help from your post.

    Best Regards,
    Li Wang


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, October 5, 2016 1:24 AM