Skip to content

WPF Flip View

In this article I will show you the implementation of a WPF Flip View.

Intruduction

A Flip View is a useful control if you want the user to walk through a list of data objects one by one. Through Data Templates you can define how the data objects are rendered on the UI.

The listing below shows the usage of the Flip View.

<flipview:FlipView Grid.Row="1" ItemsSource="{Binding Items}" ArrowsOnlyVisibleOnMouseOver="False" Orientation="Horizontal" Margin="1.5" ShowNavigationArrows="True" CurrentItemIndex="{Binding SelectedPersonIndex, Mode=TwoWay}" BorderBrush="Black" BorderThickness="1"> <flipview:FlipView.ItemTemplate> <DataTemplate> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Image Source="{Binding ImageSource}" Grid.Row="0"/> <TextBox Text="{Binding Description}" Grid.Row="1"/> </Grid> </DataTemplate> </flipview:FlipView.ItemTemplate> </flipview:FlipView>
Code language: HTML, XML (xml)

Before we start with the implementation we should write down the requirements.

Requirements

  1. The FlipView shall be able to render any data using data templates
    • it shall be possible to pass a DataTemplate to be used to render the items
    • it shall be possible to pass an Item Template Selector where different Data Templates can be choosen dynamically for each item in the Items Source
  2. There shall be arrows to witch between the items.
  3. It shall be possible to make the arrows invisible so that the user of the FlipView can implement its own navigation.
    • to support this there shall be a bindable property for the current item index
  4. The orientation shall be configurable, either horizontal or vertical
  5. Switching between the items shall be smooth (animated)

The question now is how can we organize our data and how can we implement the flip operation? The flip operation should be smooth. I.e. the items shall smoothly move out and move in. I decided to implement the flip operation using a ScrollViewer. Therefore, we will create an internal helper class and derived from ScrollViewer.

Requirement for the Scroll Viewer:

  • Itemwise scrolling
  • Smooth scrolling
  • The control is internal and cannot be used outside our assembly. It will only be created to be used by our FlipView

Finally, we will create the FlipView class.

Lets start with the Smooth Scroll Viewer.

Implementation of a Smooth Scroll Viewer

First we create a new internal class and derive from ScrollViewer. ScrollViewer already has all the functionality to scroll some content.

internal class AnimatedItemsScrollViewer : ScrollViewer { }
Code language: C# (cs)

We add a dependency property for the orientation so that the user of the control can define if the items shall be moved horizontally or vertically.

public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation", typeof(Orientation), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(Orientation.Horizontal));
Code language: C# (cs)

We will need a possibility to set/get the index of the current item. Usually, we would create a dependency property so that it can be used in xaml. But since we will access the property only from code inside the Flip View we can create a simple .Net property.

public int CurrentItemIndex { get => _currentItemIdx; set { _currentItemIdx = value; AdjustCurrentItemIdx(true); } }
Code language: C# (cs)

As the ScrollViewer ships some behavior for mouse wheel and keyboard (i.e. scroll when mouse wheel is moved or when arrow keys are pressed) we need to disable them as it is not desired for our use case.

private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; }
Code language: C# (cs)

We handle the OnPreview event of the mouse wheel and set the Handled property to true. This will stop further handling of that event.

protected override void OnKeyDown(KeyEventArgs e) { //disable keys (e.g. left, right) }
Code language: C# (cs)

We override the OnKeyDown method with an empty method that does nothing.

The animation of the scrolling turned out to be a bit tricky as for some reason the VerticalOffset and HorizontalOffset properties of ScrollViewer are not implmented as dependency properties. The problem is that we can only animate dependency properties.

To solve that problem I implemented my own depedency properties that delegate the work.

#region helper dependency properties as scrollbars are not animatable by default internal double VerticalScrollOffset { get { return (double)GetValue(VerticalScrollOffsetProperty); } set { SetValue(VerticalScrollOffsetProperty, value); } } internal static readonly DependencyProperty VerticalScrollOffsetProperty = DependencyProperty.Register("VerticalScrollOffset", typeof(double), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnVerticalScrollOffsetChanged))); private static void OnVerticalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var smoothScrollViewer = (AnimatedItemsScrollViewer)d; smoothScrollViewer.VerticalOffset = (double)e.NewValue; } internal double HorizontalScrollOffset { get { return (double)GetValue(HorizontalScrollOffsetProperty); } set { SetValue(HorizontalScrollOffsetProperty, value); } } internal static readonly DependencyProperty HorizontalScrollOffsetProperty = DependencyProperty.Register("HorizontalScrollOffset", typeof(double), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalScrollOffsetChanged))); private static void OnHorizontalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var smoothScrollViewer = (AnimatedItemsScrollViewer)d; smoothScrollViewer.HorizontalOffset = (double)e.NewValue; } public new double HorizontalOffset { get => ScrollInfo.HorizontalOffset; set => ScrollInfo.SetHorizontalOffset(value); } public new double VerticalOffset { get => ScrollInfo.VerticalOffset; set => ScrollInfo.SetVerticalOffset(value); } #endregion
Code language: C# (cs)

Finally we need to implement the calculation and animation of the itemwise scrolling.

private void OnSizeChanged(object sender, SizeChangedEventArgs e) { AdjustCurrentItemIdx(false); } private double CalcCurrentOffset() { return (Orientation == Orientation.Horizontal ? ActualWidth : ActualHeight) * CurrentItemIndex; } private void AdjustCurrentItemIdx(bool animate) { var targetValue = CalcCurrentOffset(); //make a smooth animation that starts and ends slowly var keyFramesAnimation = new DoubleAnimationUsingKeyFrames(); var duration = animate ? 500 : 0;//ms keyFramesAnimation.Duration = TimeSpan.FromMilliseconds(duration); keyFramesAnimation.KeyFrames.Add( new SplineDoubleKeyFrame( targetValue, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(duration)), new KeySpline(0.5, 0.0, 0.5, 1.0) ) ); BeginAnimation(Orientation == Orientation.Horizontal ? HorizontalScrollOffsetProperty : VerticalScrollOffsetProperty, keyFramesAnimation); }
Code language: C# (cs)

Complete Listing of the AnimatedItemsScrollViewer:

using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media.Animation; internal class AnimatedItemsScrollViewer : ScrollViewer { #region private fields private int _currentItemIdx = 0; #endregion #region constructor public AnimatedItemsScrollViewer() { SizeChanged += OnSizeChanged; PreviewMouseWheel += OnPreviewMouseWheel; } #endregion #region public properties public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation", typeof(Orientation), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(Orientation.Horizontal)); public int CurrentItemIndex { get => _currentItemIdx; set { _currentItemIdx = value; AdjustCurrentItemIdx(true); } } #endregion #region not exposed methods private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e) { e.Handled = true; } private void OnSizeChanged(object sender, SizeChangedEventArgs e) { AdjustCurrentItemIdx(false); } private double CalcCurrentOffset() { return (Orientation == Orientation.Horizontal ? ActualWidth : ActualHeight) * CurrentItemIndex; } private void AdjustCurrentItemIdx(bool animate) { var targetValue = CalcCurrentOffset(); //make a smooth animation that starts and ends slowly var keyFramesAnimation = new DoubleAnimationUsingKeyFrames(); var duration = animate ? 500 : 0;//ms keyFramesAnimation.Duration = TimeSpan.FromMilliseconds(duration); keyFramesAnimation.KeyFrames.Add( new SplineDoubleKeyFrame( targetValue, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(duration)), new KeySpline(0.5, 0.0, 0.5, 1.0) ) ); BeginAnimation(Orientation == Orientation.Horizontal ? HorizontalScrollOffsetProperty : VerticalScrollOffsetProperty, keyFramesAnimation); } protected override void OnKeyDown(KeyEventArgs e) { //disable keys (e.g. left, right) } #endregion #region helper dependency properties as scrollbars are not animatable by default internal double VerticalScrollOffset { get { return (double)GetValue(VerticalScrollOffsetProperty); } set { SetValue(VerticalScrollOffsetProperty, value); } } internal static readonly DependencyProperty VerticalScrollOffsetProperty = DependencyProperty.Register("VerticalScrollOffset", typeof(double), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnVerticalScrollOffsetChanged))); private static void OnVerticalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var smoothScrollViewer = (AnimatedItemsScrollViewer)d; smoothScrollViewer.VerticalOffset = (double)e.NewValue; } internal double HorizontalScrollOffset { get { return (double)GetValue(HorizontalScrollOffsetProperty); } set { SetValue(HorizontalScrollOffsetProperty, value); } } internal static readonly DependencyProperty HorizontalScrollOffsetProperty = DependencyProperty.Register("HorizontalScrollOffset", typeof(double), typeof(AnimatedItemsScrollViewer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnHorizontalScrollOffsetChanged))); private static void OnHorizontalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var smoothScrollViewer = (AnimatedItemsScrollViewer)d; smoothScrollViewer.HorizontalOffset = (double)e.NewValue; } public new double HorizontalOffset { get => ScrollInfo.HorizontalOffset; set => ScrollInfo.SetHorizontalOffset(value); } public new double VerticalOffset { get => ScrollInfo.VerticalOffset; set => ScrollInfo.SetVerticalOffset(value); } #endregion }
Code language: C# (cs)

Implementation of the Flip View Control

As we are done with the smooth scroll viewer we can start with the implementation of the Flip View.

To implement the Flip View we will need a control class and a control template. We will implement the control logic inside the control class. The control template will contain the xaml used to define the appearance.

We add a new class and derive from Control. Furthermore, we need to add a static constructor and override the default style meta data. That will tell WPF to search for a style with target type FlipView (see control template section below).

Why do we derive from Control? We don’t need some special base functionality so we can use Control as baseclass. If we would implement a control that can have some visual content (like a Button) I would choose ContentControl. So it depends on the requirements what base class to choose (as we did when we derived from ScrollViewer above).

public class FlipView : Control { #region constructor static FlipView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipView), new FrameworkPropertyMetadata(typeof(FlipView))); } }
Code language: C# (cs)

We need some dependency properties to make the Flip View usable in xaml:

  • IEnumerable<object> ItemsSource: To bind to items to be shown in the Flip View
  • DataTemplate ItemTemplate: DataTemplate to be used to render the items
  • DataTemplateSelector ItemTemplateSelector: DataTemplate selector that can be used if the items need different data templates
  • int CurrentItemIndex: Index of the current item
  • bool ArrowsOnlyVisibleOnMouseOver: Determines if the arrows become visible when the mouse hovers over the arrows or when the mouse hovers over the Flip View
  • Orientation Orientation: Determines the orientation of the Flip View
  • bool ShowNavigationArrows: Determines if the navigation arrows shall be shown
  • Brush ArrowBrush: The brush of the arrows
public IEnumerable<object> ItemsSource { get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( "ItemsSource", typeof(IEnumerable<object>), typeof(FlipView), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged))); public DataTemplate ItemTemplate { get { return (DataTemplate)GetValue(ItemTemplateProperty); } set { SetValue(ItemTemplateProperty, value); } } public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register( "ItemTemplate", typeof(DataTemplate), typeof(FlipView)); public int CurrentItemIndex { get { return (int)GetValue(CurrentItemIndexProperty); } set { SetValue(CurrentItemIndexProperty, value); } } public static readonly DependencyProperty CurrentItemIndexProperty = DependencyProperty.Register( "CurrentItemIndex", typeof(int), typeof(FlipView), new PropertyMetadata(0, new PropertyChangedCallback(OnCurrentItemIndexPropertyChanged))); public bool ArrowsOnlyVisibleOnMouseOver { get { return (bool)GetValue(ArrowsOnlyVisibleOnMouseOverProperty); } set { SetValue(ArrowsOnlyVisibleOnMouseOverProperty, value); } } public static readonly DependencyProperty ArrowsOnlyVisibleOnMouseOverProperty = DependencyProperty.Register( "ArrowsOnlyVisibleOnMouseOver", typeof(bool), typeof(FlipView)); public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation", typeof(Orientation), typeof(FlipView), new PropertyMetadata(Orientation.Horizontal)); public bool ShowNavigationArrows { get { return (bool)GetValue(ShowNavigationArrowsProperty); } set { SetValue(ShowNavigationArrowsProperty, value); } } public static readonly DependencyProperty ShowNavigationArrowsProperty = DependencyProperty.Register( "ShowNavigationArrows", typeof(bool), typeof(FlipView), new PropertyMetadata(true)); public DataTemplateSelector ItemTemplateSelector { get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); } set { SetValue(ItemTemplateSelectorProperty, value); } } public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register( "ItemTemplateSelector", typeof(DataTemplateSelector), typeof(FlipView)); public Brush ArrowBrush { get { return (Brush)GetValue(ArrowBrushProperty); } set { SetValue(ArrowBrushProperty, value); } } public static readonly DependencyProperty ArrowBrushProperty = DependencyProperty.Register( "ArrowBrush", typeof(Brush), typeof(FlipView), new PropertyMetadata(new SolidColorBrush(Colors.CornflowerBlue)));
Code language: C# (cs)

Then we need to interact with the UI elements in the control template. We need an ItemsControl, the animated scroll viewer and 4 arrows (up, down, left, right). To interact with these elements we need to define the names in our control. If someone wants to define a new control template the names must match. Otherwise, the control cannot find the UI elements. The method where we wire the control to the control template is called OnApplyTemplate which is a virtual method declared in FrameworkElement. We simply need to override it.

Hint: The method GetTemplateChild is important to find the UI Elements inside the control template.

private ItemsControl _itemsControl; private AnimatedItemsScrollViewer _scrollViewer; private Image _rightArrow; private Image _leftArrow; private Image _upArrow; private Image _downArrow; public override void OnApplyTemplate() { _itemsControl = GetTemplateChild("PART_ItemsControl") as ItemsControl; _itemsControl.DataContext = this; _scrollViewer = GetTemplateChild("PART_ScrollViewer") as AnimatedItemsScrollViewer; _scrollViewer.CanContentScroll = false; _rightArrow = GetTemplateChild("PART_RightArrow") as Image; _rightArrow.MouseLeftButtonDown += RightDownArrow_MouseLeftButtonDown; _downArrow = GetTemplateChild("PART_DownArrow") as Image; _downArrow.MouseLeftButtonDown += RightDownArrow_MouseLeftButtonDown; _leftArrow = GetTemplateChild("PART_LeftArrow") as Image; _leftArrow.MouseLeftButtonDown += LeftUpArrow_MouseLeftButtonDown; _upArrow = GetTemplateChild("PART_UpArrow") as Image; _upArrow.MouseLeftButtonDown += LeftUpArrow_MouseLeftButtonDown; base.OnApplyTemplate(); } private static void OnCurrentItemIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = (FlipView)d; flipView._scrollViewer.CurrentItemIndex = flipView.CurrentItemIndex; } private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = (FlipView)d; flipView.CurrentItemIndex = 0; } private void RightDownArrow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var newIndex = CurrentItemIndex + 1; if (newIndex < ItemsSource.Count()) CurrentItemIndex = newIndex; } private void LeftUpArrow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var newIndex = CurrentItemIndex - 1; if (newIndex >= 0) CurrentItemIndex = newIndex; }
Code language: C# (cs)

Finally, we can add some contracts definition to our class so that a control template designer knows on what UI elements our control depends on. Therefore, we simply add the following attirbutes to the FlipControl class.

[TemplatePart(Name = "PART_ItemsControl", Type = typeof(ItemsControl))] [TemplatePart(Name = "PART_ScrollViewer", Type = typeof(AnimatedItemsScrollViewer))] [TemplatePart(Name = "PART_RightArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_DownArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_LeftArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_UpArrow", Type = typeof(Image))] public class FlipView : Control
Code language: C# (cs)

Hint: It is a common naming convention to prefix mandatory parts of a control template with “PART_”. So, it is a good practice to do it but it is not mandatory.

Complete Listing of the Flip View:

using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; [TemplatePart(Name = "PART_ItemsControl", Type = typeof(ItemsControl))] [TemplatePart(Name = "PART_ScrollViewer", Type = typeof(AnimatedItemsScrollViewer))] [TemplatePart(Name = "PART_RightArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_DownArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_LeftArrow", Type = typeof(Image))] [TemplatePart(Name = "PART_UpArrow", Type = typeof(Image))] public class FlipView : Control { #region private fields private ItemsControl _itemsControl; private AnimatedItemsScrollViewer _scrollViewer; private Image _rightArrow; private Image _leftArrow; private Image _upArrow; private Image _downArrow; #endregion #region constructor static FlipView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipView), new FrameworkPropertyMetadata(typeof(FlipView))); } #endregion #region dependency properties public IEnumerable<object> ItemsSource { get { return (IEnumerable<object>)GetValue(ItemsSourceProperty); } set { SetValue(ItemsSourceProperty, value); } } public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register( "ItemsSource", typeof(IEnumerable<object>), typeof(FlipView), new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged))); public DataTemplate ItemTemplate { get { return (DataTemplate)GetValue(ItemTemplateProperty); } set { SetValue(ItemTemplateProperty, value); } } public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register( "ItemTemplate", typeof(DataTemplate), typeof(FlipView)); public int CurrentItemIndex { get { return (int)GetValue(CurrentItemIndexProperty); } set { SetValue(CurrentItemIndexProperty, value); } } public static readonly DependencyProperty CurrentItemIndexProperty = DependencyProperty.Register( "CurrentItemIndex", typeof(int), typeof(FlipView), new PropertyMetadata(0, new PropertyChangedCallback(OnCurrentItemIndexPropertyChanged))); public bool ArrowsOnlyVisibleOnMouseOver { get { return (bool)GetValue(ArrowsOnlyVisibleOnMouseOverProperty); } set { SetValue(ArrowsOnlyVisibleOnMouseOverProperty, value); } } public static readonly DependencyProperty ArrowsOnlyVisibleOnMouseOverProperty = DependencyProperty.Register( "ArrowsOnlyVisibleOnMouseOver", typeof(bool), typeof(FlipView)); public Orientation Orientation { get { return (Orientation)GetValue(OrientationProperty); } set { SetValue(OrientationProperty, value); } } public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register( "Orientation", typeof(Orientation), typeof(FlipView), new PropertyMetadata(Orientation.Horizontal)); public bool ShowNavigationArrows { get { return (bool)GetValue(ShowNavigationArrowsProperty); } set { SetValue(ShowNavigationArrowsProperty, value); } } public static readonly DependencyProperty ShowNavigationArrowsProperty = DependencyProperty.Register( "ShowNavigationArrows", typeof(bool), typeof(FlipView), new PropertyMetadata(true)); public DataTemplateSelector ItemTemplateSelector { get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); } set { SetValue(ItemTemplateSelectorProperty, value); } } public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register( "ItemTemplateSelector", typeof(DataTemplateSelector), typeof(FlipView)); public Brush ArrowBrush { get { return (Brush)GetValue(ArrowBrushProperty); } set { SetValue(ArrowBrushProperty, value); } } public static readonly DependencyProperty ArrowBrushProperty = DependencyProperty.Register( "ArrowBrush", typeof(Brush), typeof(FlipView), new PropertyMetadata(new SolidColorBrush(Colors.CornflowerBlue))); #endregion #region public methods public override void OnApplyTemplate() { _itemsControl = GetTemplateChild("PART_ItemsControl") as ItemsControl; _itemsControl.DataContext = this; _scrollViewer = GetTemplateChild("PART_ScrollViewer") as AnimatedItemsScrollViewer; _scrollViewer.CanContentScroll = false; _rightArrow = GetTemplateChild("PART_RightArrow") as Image; _rightArrow.MouseLeftButtonDown += RightDownArrow_MouseLeftButtonDown; _downArrow = GetTemplateChild("PART_DownArrow") as Image; _downArrow.MouseLeftButtonDown += RightDownArrow_MouseLeftButtonDown; _leftArrow = GetTemplateChild("PART_LeftArrow") as Image; _leftArrow.MouseLeftButtonDown += LeftUpArrow_MouseLeftButtonDown; _upArrow = GetTemplateChild("PART_UpArrow") as Image; _upArrow.MouseLeftButtonDown += LeftUpArrow_MouseLeftButtonDown; base.OnApplyTemplate(); } #endregion #region not exposed methods private static void OnCurrentItemIndexPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = (FlipView)d; flipView._scrollViewer.CurrentItemIndex = flipView.CurrentItemIndex; } private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var flipView = (FlipView)d; flipView.CurrentItemIndex = 0; } private void RightDownArrow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var newIndex = CurrentItemIndex + 1; if (newIndex < ItemsSource.Count()) CurrentItemIndex = newIndex; } private void LeftUpArrow_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var newIndex = CurrentItemIndex - 1; if (newIndex >= 0) CurrentItemIndex = newIndex; } #endregion }
Code language: C# (cs)

Default Control Template for Flip View

In order to give the Flip View an appearance we need to define a default control template. By convention it is placed in the project in Themes/Generic.xaml. It is important to set the TargetType to FlipView. WPF will search for a style with target type FlipView as we defined in the static constructor of our FlipView.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:converter="clr-namespace:FlipView.Converter" xmlns:local="clr-namespace:FlipView"> <Style TargetType="{x:Type local:FlipView}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:FlipView}"> <ControlTemplate.Resources> <converter:SelectStyleConverter x:Key="SelectStyleConverterKey"> <converter:SelectStyleConverter.SelectiveHoverOverStyle> <Style> <Style.Triggers> <EventTrigger RoutedEvent="Image.MouseEnter"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="0.8" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="Image.MouseLeave"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.8" To="0.0" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style> </converter:SelectStyleConverter.SelectiveHoverOverStyle> <converter:SelectStyleConverter.NonSelectiveHoverOverStyle> <Style> <Style.Triggers> <DataTrigger Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type Grid}}}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.0" To="0.8" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0.8" To="0.0" Duration="0:0:0.2"/> </Storyboard> </BeginStoryboard> </DataTrigger.ExitActions> </DataTrigger> </Style.Triggers> </Style> </converter:SelectStyleConverter.NonSelectiveHoverOverStyle> </converter:SelectStyleConverter> <converter:ArrowVisibilityConverter x:Key="LeftArrowVisibilityConverterKey" ArrowDirection="Left"/> <converter:ArrowVisibilityConverter x:Key="RightArrowVisibilityConverterKey" ArrowDirection="Right"/> <converter:ArrowVisibilityConverter x:Key="UpArrowVisibilityConverterKey" ArrowDirection="Up"/> <converter:ArrowVisibilityConverter x:Key="DownArrowVisibilityConverterKey" ArrowDirection="Down"/> </ControlTemplate.Resources> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Grid> <local:AnimatedItemsScrollViewer x:Name="PART_ScrollViewer" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" Orientation="{Binding Path=Orientation, RelativeSource={RelativeSource TemplatedParent}}"> <ItemsControl x:Name="PART_ItemsControl" ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource TemplatedParent}}"> <ItemsControl.ItemTemplate> <DataTemplate> <ContentControl Content="{Binding}" Width="{Binding Path=ActualWidth, ElementName=PART_ScrollViewer}" Height="{Binding Path=ActualHeight, ElementName=PART_ScrollViewer}" ContentTemplate="{Binding Path=DataContext.ItemTemplate, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" ContentTemplateSelector="{Binding Path=DataContext.ItemTemplateSelector, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/> </DataTemplate> </ItemsControl.ItemTemplate> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel Orientation="{Binding Path=Orientation, RelativeSource={RelativeSource AncestorType={x:Type local:AnimatedItemsScrollViewer}}}"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl> </local:AnimatedItemsScrollViewer> <Image x:Name="PART_LeftArrow" Height="40" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center" Opacity="0" Style="{Binding ArrowsOnlyVisibleOnMouseOver, Converter={StaticResource SelectStyleConverterKey}, RelativeSource={RelativeSource TemplatedParent}}" Margin="5,0,0,0"> <Image.Visibility> <MultiBinding Converter="{StaticResource LeftArrowVisibilityConverterKey}"> <Binding Path="Orientation" RelativeSource="{RelativeSource TemplatedParent}"/> <Binding Path="ShowNavigationArrows" RelativeSource="{RelativeSource TemplatedParent}"/> </MultiBinding> </Image.Visibility> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <GeometryDrawing Brush="{Binding ArrowBrush, RelativeSource={RelativeSource TemplatedParent}}" Geometry="M 60 60 L 60 0 L 30 30 Z" /> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> <Image x:Name="PART_RightArrow" Height="40" Width="20" HorizontalAlignment="Right" VerticalAlignment="Center" Opacity="0" Style="{Binding ArrowsOnlyVisibleOnMouseOver, Converter={StaticResource SelectStyleConverterKey}, RelativeSource={RelativeSource TemplatedParent}}" Margin="0,0,5,0"> <Image.Visibility> <MultiBinding Converter="{StaticResource RightArrowVisibilityConverterKey}"> <Binding Path="Orientation" RelativeSource="{RelativeSource TemplatedParent}"/> <Binding Path="ShowNavigationArrows" RelativeSource="{RelativeSource TemplatedParent}"/> </MultiBinding> </Image.Visibility> <Image.Source> <DrawingImage> <DrawingImage.Drawing> <GeometryDrawing Brush="{Binding ArrowBrush, RelativeSource={RelativeSource TemplatedParent}}" Geometry="M 0 0 L 0 60 L 30 30 Z" /> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> <Image x:Name="PART_UpArrow" Height="20" Width="40" HorizontalAlignment="Center" VerticalAlignment="Top" Opacity="0" Style="{Binding ArrowsOnlyVisibleOnMouseOver, Converter={StaticResource SelectStyleConverterKey}, RelativeSource={RelativeSource TemplatedParent}}" Margin="0,5,0,0"> <Image.Visibility> <MultiBinding Converter="{StaticResource UpArrowVisibilityConverterKey}"> <Binding Path="Orientation" RelativeSource="{RelativeSource TemplatedParent}"/> <Binding Path="ShowNavigationArrows" RelativeSource="{RelativeSource TemplatedParent}"/> </MultiBinding> </Image.Visibility><Image.Source> <DrawingImage> <DrawingImage.Drawing> <GeometryDrawing Brush="{Binding ArrowBrush, RelativeSource={RelativeSource TemplatedParent}}" Geometry="M 0 60 L 60 60 L 30 30 Z" /> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> <Image x:Name="PART_DownArrow" Height="20" Width="40" HorizontalAlignment="Center" VerticalAlignment="Bottom" Opacity="0" Style="{Binding ArrowsOnlyVisibleOnMouseOver, Converter={StaticResource SelectStyleConverterKey}, RelativeSource={RelativeSource TemplatedParent}}" Margin="0,0,0,5"> <Image.Visibility> <MultiBinding Converter="{StaticResource DownArrowVisibilityConverterKey}"> <Binding Path="Orientation" RelativeSource="{RelativeSource TemplatedParent}"/> <Binding Path="ShowNavigationArrows" RelativeSource="{RelativeSource TemplatedParent}"/> </MultiBinding> </Image.Visibility><Image.Source> <DrawingImage> <DrawingImage.Drawing> <GeometryDrawing Brush="{Binding ArrowBrush, RelativeSource={RelativeSource TemplatedParent}}" Geometry="M 0 0 L 60 0 L 30 30 Z" /> </DrawingImage.Drawing> </DrawingImage> </Image.Source> </Image> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Code language: HTML, XML (xml)

Converters

public class ArrowVisibilityConverter : IMultiValueConverter { public ArrowDirection ArrowDirection { get; set; } public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values?.Count() != 2) return null; var orientation = (Orientation)values[0]; var showArrows = (bool)values[1]; if (showArrows == false) return Visibility.Collapsed; if ((ArrowDirection == ArrowDirection.Left || ArrowDirection == ArrowDirection.Right) && orientation == Orientation.Horizontal) return Visibility.Visible; if ((ArrowDirection == ArrowDirection.Up || ArrowDirection == ArrowDirection.Down) && orientation == Orientation.Vertical) return Visibility.Visible; return Visibility.Collapsed; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { return null; } } public class SelectStyleConverter : IValueConverter { public Style SelectiveHoverOverStyle { get; set; } public Style NonSelectiveHoverOverStyle { get; set; } public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { var useSelectiveStyle = (bool)value; if (useSelectiveStyle) return SelectiveHoverOverStyle; return NonSelectiveHoverOverStyle; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
Code language: C# (cs)

6 Comments

  1. Alex Alex

    the converters are missing 🙁

    • sorry, adden the converters 😉

      • Sri Sri

        where u added the converters?

        • At the bottom of this site…. what converters are you missing else?

  2. Anonymous Anonymous

    Whre u added the converters

    • At the bottom of this site… what converters are you missing else?

Leave a Reply

Your email address will not be published. Required fields are marked *

Copyright (c) by Thomas Kemp, 2021