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:
- Custom Control Development
- 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.