public partial class StoreCarousel : Control { private const string PART_Root = "PART_Root"; private const string PART_OldGrid = "PART_OldGrid"; private const string PART_NewGrid = "PART_NewGrid"; private const string PART_OldImage = "PART_OldImage"; private const string PART_NewImage = "PART_NewImage"; private const string PART_PipsPager = "PART_PipsPager"; private const string PART_OldTransform = "PART_OldTransform"; private const string PART_NewTransform = "PART_NewTransform"; private Grid root; private Grid oldGrid; private Grid newGrid; private Image oldImage; private Image newImage; private PipsPager pipsPager; private TranslateTransform oldTranslateTransform; private TranslateTransform newTranslateTransform; private readonly List _images = new(); private int _index = 0; private DispatcherTimer _timer; private bool isUpdatingPager; private int _previousIndex = -1; public string Title { get { return (string)GetValue(TitleProperty); } set { SetValue(TitleProperty, value); } } public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(StoreCarousel), new PropertyMetadata(null)); public object ActionContent { get { return (object)GetValue(ActionContentProperty); } set { SetValue(ActionContentProperty, value); } } public static readonly DependencyProperty ActionContentProperty = DependencyProperty.Register(nameof(ActionContent), typeof(object), typeof(StoreCarousel), new PropertyMetadata(null)); public string Description { get { return (string)GetValue(DescriptionProperty); } set { SetValue(DescriptionProperty, value); } } public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register(nameof(Description), typeof(string), typeof(StoreCarousel), new PropertyMetadata(null)); public TimeSpan AnimationDuration { get { return (TimeSpan)GetValue(AnimationDurationProperty); } set { SetValue(AnimationDurationProperty, value); } } public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register(nameof(AnimationDuration), typeof(TimeSpan), typeof(StoreCarousel), new PropertyMetadata(TimeSpan.FromMilliseconds(800))); public TimeSpan TimerInterval { get { return (TimeSpan)GetValue(TimerIntervalProperty); } set { SetValue(TimerIntervalProperty, value); } } public static readonly DependencyProperty TimerIntervalProperty = DependencyProperty.Register(nameof(TimerInterval), typeof(TimeSpan), typeof(StoreCarousel), new PropertyMetadata(TimeSpan.FromSeconds(2), OnTimerIntervalChanged)); private static void OnTimerIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (StoreCarousel)d; if (ctl != null && ctl._timer != null) { ctl._timer.Interval = (TimeSpan)e.NewValue; } } public bool AutoShuffle { get => (bool)GetValue(AutoShuffleProperty); set => SetValue(AutoShuffleProperty, value); } public static readonly DependencyProperty AutoShuffleProperty = DependencyProperty.Register(nameof(AutoShuffle), typeof(bool), typeof(StoreCarousel), new PropertyMetadata(true, OnAutoShuffleChanged)); private static void OnAutoShuffleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (StoreCarousel)d; if (ctl != null && ctl._timer != null) { if ((bool)e.NewValue) ctl._timer.Start(); else ctl._timer.Stop(); } } public enum CarouselSlideDirection { LeftToRight, RightToLeft, Auto } public enum PipsPagerVisibilityMode { Visible, Collapsed, VisibleByHover } public PipsPagerVisibilityMode PipsPagerVisibility { get { return (PipsPagerVisibilityMode)GetValue(PipsPagerVisibilityProperty); } set { SetValue(PipsPagerVisibilityProperty, value); } } public static readonly DependencyProperty PipsPagerVisibilityProperty = DependencyProperty.Register(nameof(PipsPagerVisibility), typeof(PipsPagerVisibilityMode), typeof(StoreCarousel), new PropertyMetadata(PipsPagerVisibilityMode.Visible, OnPipsPagerVisibilityChanged)); private static void OnPipsPagerVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (StoreCarousel)d; if (ctl != null) { ctl.UpdatePipsPagerVisibility(); } } private void UpdatePipsPagerVisibility() { if (pipsPager == null) { return; } switch (PipsPagerVisibility) { case PipsPagerVisibilityMode.Visible: pipsPager.Visibility = Visibility.Visible; break; case PipsPagerVisibilityMode.Collapsed: pipsPager.Visibility = Visibility.Collapsed; break; case PipsPagerVisibilityMode.VisibleByHover: pipsPager.Visibility = Visibility.Collapsed; break; } } protected override void OnPointerEntered(PointerRoutedEventArgs e) { base.OnPointerEntered(e); if (PipsPagerVisibility == PipsPagerVisibilityMode.VisibleByHover && pipsPager != null) { pipsPager.Visibility = Visibility.Visible; } } protected override void OnPointerExited(PointerRoutedEventArgs e) { base.OnPointerExited(e); if (PipsPagerVisibility == PipsPagerVisibilityMode.VisibleByHover && pipsPager != null) { pipsPager.Visibility = Visibility.Collapsed; } } public CarouselSlideDirection SlideDirection { get => (CarouselSlideDirection)GetValue(SlideDirectionProperty); set => SetValue(SlideDirectionProperty, value); } public static readonly DependencyProperty SlideDirectionProperty = DependencyProperty.Register(nameof(SlideDirection), typeof(CarouselSlideDirection), typeof(StoreCarousel), new PropertyMetadata(CarouselSlideDirection.RightToLeft)); public StoreCarousel() { DefaultStyleKey = typeof(StoreCarousel); } protected override void OnApplyTemplate() { base.OnApplyTemplate(); root = GetTemplateChild(PART_Root) as Grid; oldGrid = GetTemplateChild(PART_OldGrid) as Grid; newGrid = GetTemplateChild(PART_NewGrid) as Grid; oldImage = GetTemplateChild(PART_OldImage) as Image; newImage = GetTemplateChild(PART_NewImage) as Image; pipsPager = GetTemplateChild(PART_PipsPager) as PipsPager; oldTranslateTransform = GetTemplateChild(PART_OldTransform) as TranslateTransform; newTranslateTransform = GetTemplateChild(PART_NewTransform) as TranslateTransform; pipsPager.SelectedIndexChanged += OnPipsPagerSelectedIndexChanged; if (_images.Count > 0) { oldImage.Source = new BitmapImage(new(_images[0])); pipsPager.NumberOfPages = _images.Count; } _timer = new DispatcherTimer(); _timer.Interval = TimerInterval; _timer.Tick += (_, _) => StartTransition(); if (AutoShuffle) { _timer.Start(); } } private void OnPipsPagerSelectedIndexChanged(PipsPager sender, PipsPagerSelectedIndexChangedEventArgs args) { if (pipsPager == null || isUpdatingPager) return; GoToIndex(pipsPager.SelectedPageIndex); } public void GoToIndex(int index) { if (_images.Count < 1) return; if (index < 0 || index >= _images.Count) return; _index = index; StartTransition(); } public void SetImages(IEnumerable images) { _images.Clear(); _images.AddRange(images); if (oldImage != null) { oldImage.Source = new BitmapImage(new(_images[0])); } if (pipsPager != null) { pipsPager.NumberOfPages = _images.Count; } } private void StartTransition() { if (_images.Count < 2) return; _timer.Stop(); if (pipsPager != null) { isUpdatingPager = true; pipsPager.SelectedPageIndex = _index; isUpdatingPager = false; } int previousIndex = _previousIndex >= 0 ? _previousIndex : _index; _previousIndex = _index; _index = (_index + 1) % _images.Count; newImage.Source = new BitmapImage(new(_images[_index])); double width = root.ActualWidth; oldTranslateTransform.X = 0; var oldAnim = new DoubleAnimation { From = 0, Duration = AnimationDuration, EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } }; var newAnim = new DoubleAnimation { To = 0, Duration = AnimationDuration, EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } }; CarouselSlideDirection effectiveDirection = SlideDirection; if (SlideDirection == CarouselSlideDirection.Auto) { if (_index > previousIndex || pipsPager?.SelectedPageIndex > _index) effectiveDirection = CarouselSlideDirection.RightToLeft; // next else effectiveDirection = CarouselSlideDirection.LeftToRight; // previous } switch (effectiveDirection) { case CarouselSlideDirection.LeftToRight: newTranslateTransform.X = -width; oldAnim.To = width; newAnim.From = -width; break; case CarouselSlideDirection.RightToLeft: newTranslateTransform.X = width; oldAnim.To = -width; newAnim.From = width; break; } var sb = new Storyboard(); Storyboard.SetTarget(oldAnim, oldTranslateTransform); Storyboard.SetTargetProperty(oldAnim, "X"); Storyboard.SetTarget(newAnim, newTranslateTransform); Storyboard.SetTargetProperty(newAnim, "X"); sb.Children.Add(oldAnim); sb.Children.Add(newAnim); sb.Completed += (_, _) => { oldImage.Source = newImage.Source; oldTranslateTransform.X = 0; switch (effectiveDirection) { case CarouselSlideDirection.LeftToRight: newTranslateTransform.X = -width; break; case CarouselSlideDirection.RightToLeft: newTranslateTransform.X = width; break; } if (AutoShuffle) { _timer.Start(); } }; sb.Begin(); } }