Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF FlipView

0.00/5 (No votes)
8 Mar 2014 1  
A FlipView control for WPF, which behaves exactly as Windows Store XAML FlipView.

Introduction

FlipView control behaves like an ItemsControl and shows items one by one on swipe gesture. Also navigation buttons will be available to navigate using mouse. Many users nowadays are expecting Windows store touch apps to be developed using WPF. That doesn't require to be published on store for distribution. Same time all the features and behaviors that can be done in WinRT can also be done in WPF. So many users prefer that.

Background

So it is better to have most of the controls in WinRT to be ported to WPF. Some third party toolkits support most of the controls. But I found FlipView is missing such way. So I thought of writing a control for that behavior.

It is better to have basic understanding of the following concepts to clearly grasp this control:

  1. Custom Control Development
  2. Touch manipulation

Basic Structure

The basic structure is simple. The template of FlipView will have three containers to store current, next and previous item. On clicking navigation buttons or on swipe, the root grid which holds the entire template will animated and items will be updated based on SelectedIndex.

Below is the ControlTemplate of FlipView.

<Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <Grid ClipToBounds="True"
                              x:Name="PART_Container">
                            <local:FlipViewPanel x:Name="PART_Root"
                                                 IsManipulationEnabled="True"
                                                 Background="Transparent">
                                <ContentControl x:Name="PART_PreviousItem"
                                                ContentTemplate="{Binding ItemTemplate, 
                                                RelativeSource={RelativeSource TemplatedParent}}" />
                                <ContentControl x:Name="PART_NextItem"
                                                ContentTemplate="{Binding ItemTemplate, 
                                                RelativeSource={RelativeSource TemplatedParent}}" />
                                <ContentControl x:Name="PART_CurrentItem"
                                                ContentTemplate="{Binding ItemTemplate, 
                                                RelativeSource={RelativeSource TemplatedParent}}" />
                            </local:FlipViewPanel>
                            <Grid VerticalAlignment="Center"
                                  x:Name="PART_ButtonPanel"
                                  Visibility="Collapsed">
                                <RepeatButton x:Name="PART_NextButton"
                                              FontFamily="Segoe UI Symbol"
                                              Content="?"
                                              FontSize="18"
                                              Style="{StaticResource NavigationButtonStyle}"
                                              Command="{x:Static local:FlipView.NextCommand}"
                                              HorizontalAlignment="Right" />
                                <RepeatButton x:Name="PART_PreviousButton"
                                              FontFamily="Segoe UI Symbol"
                                              Content="?"
                                              FontSize="18"
                                              Style="{StaticResource NavigationButtonStyle}"
                                              Command="{x:Static local:FlipView.PreviousCommand}"
                                              HorizontalAlignment="Left" />
                            </Grid>
                        </Grid>
</Border> 

Animation Factory

The Factory class will generate animations based on the to and from values. By default, the animation target property is set to Translation.X value, since the animation translation needs only horizontal direction. Two EasingDoubleKeyFrame will be created and added to animation. The KeyTime for the second frame is set to 500 milliseconds, which controls the speed of animation.

public Storyboard GetAnimation(DependencyObject target, double to, double from)
        {
            Storyboard story = new Storyboard();
            Storyboard.SetTargetProperty(story, new PropertyPath("(TextBlock.RenderTransform).(TranslateTransform.X)"));
            Storyboard.SetTarget(story, target);
 
            var doubleAnimation = new DoubleAnimationUsingKeyFrames();
 
            EasingDoubleKeyFrame fromFrame = new EasingDoubleKeyFrame(from);
            fromFrame.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
            fromFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0));
 
            EasingDoubleKeyFrame toFrame = new EasingDoubleKeyFrame(to);
            toFrame.EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut };
            toFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(200));
 
            doubleAnimation.KeyFrames.Add(fromFrame);
            doubleAnimation.KeyFrames.Add(toFrame);
            story.Children.Add(doubleAnimation);
 
            return story;
        } 

Navigation

When user clicks the Next/Previous buttons, a storyboard will be generated using AnimationFactory and it will be started. The to and from values will be based on the SelectedIndex. RoutedUICommands are available to control the action of the Next/Previous buttons.

public static RoutedUICommand NextCommand = new RoutedUICommand("Next", "Next", typeof(FlipView));
public static RoutedUICommand PreviousCommand = new RoutedUICommand("Previous", 
"Previous", typeof(FlipView)); 

Once the control reaches the end, the Next button will be disabled. This is controlled by CanExecution callback of RoutedUICommand.

private void OnNextCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.SelectedIndex < (this.Items.Count - 1);
        } 

If there is no possibility of Navigating to next item (means it reached the end), the Next button will be collapsed. This is achieved by controlling the visibility in Triggers.

<Trigger Property="IsEnabled" Value="false">
    <Setter Property="Visibility" Value="Collapsed" />
</Trigger> 

Touch Gestures

User can navigate items using swipe gestures. This is controlled by WPF built in manipulation APIs. On swiping the control, framework will fire the OnManipulationDelta callback. The translation values will be applied to the render transform of root container.

If user left the finger before he/she fully swiped the item, the control will animate the next item and bring it into position. For this, the control will track the position while user is swiping. This behavior is exactly equal to Windows 8 FlipView.

private void OnRootManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (!(this.PART_Root.RenderTransform is MatrixTransform))
            {
                this.PART_Root.RenderTransform = new MatrixTransform();
            }
 
            Matrix matrix = ((MatrixTransform)this.PART_Root.RenderTransform).Matrix;
            var delta = e.DeltaManipulation;
 
            if ((this.SelectedIndex == 0 && 
            delta.Translation.X > 0 && this.elasticFactor > 0)
                || (this.SelectedIndex == this.Items.Count - 1 && 
                delta.Translation.X < 0 && this.elasticFactor > 0))
            {
                this.elasticFactor -= 0.05;
            }
 
            matrix.Translate(delta.Translation.X * elasticFactor, 0);
            this.PART_Root.RenderTransform = new MatrixTransform(matrix);
 
            e.Handled = true;
        } 

Elastic Effect

If SelectedIndex of the control is 0, it means the first item is selected and user is not allowed to navigate to the previous item. So it is important to indicate to the user the impossibility on swiping to previous item. The control will maintain a factor variable of double value. On swiping the control, this factor will be multiplied to the OffsetX value and then applied to the control. So on no possibility of navigating to previous item, the factor will be decremented by 0.05. So user will feel a difficulty in dragging. On leaving finger, an animation will run and bring it to original position.

if ((this.SelectedIndex == 0 && delta.Translation.X > 0 && this.elasticFactor > 0)
    || (this.SelectedIndex == this.Items.Count - 1 && 
    delta.Translation.X < 0 && this.elasticFactor > 0))
{
    this.elasticFactor -= 0.05;
}
 
matrix.Translate(delta.Translation.X * elasticFactor, 0);

This is the exact behavior of WinRT FlipView. This will clearly indicate the impossibility of control to navigate to previous item.

Data Binding

The FlipView is inherited from Selector. So all the base framework operations are possible with this control. A collection can be bound to control as we do for traditional ItemsControl. Also DataTemplate can be applied to decorate the visual.

<controls:FlipView ItemsSource="{Binding Movies}"
                           SelectedIndex="0">
            <controls:FlipView.ItemTemplate>
                <DataTemplate>
                    <Image Source="{Binding Image}"
                           Stretch="Fill"/>
                </DataTemplate>
            </controls:FlipView.ItemTemplate>
</controls:FlipView> 

Note: It is mandatory to set the SelectedIndex. Otherwise, a blank page will be shown.

Points of Interest

For any WPF application we develop to mimic exactly like Windows Store app, this control will be really useful.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here