1

I'm currently working with a Slider component in WinUI that is continuously updated by a Timer. The challenge I'm facing is implementing two-way binding on this Slider. The issue arises because every time the Timer updates the Slider's value, it interrupts the user's input. I'm looking for a solution that allows the Slider to update regularly, but also respects and maintains the user's input when they are interacting with the Slider.

I tried using GotFocus and LostFocus, PointerPressed and Released, But all of them didn't work at all.

public void CurrentTimeSlider_OnValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    if (MusicState.Instance.OnDrag)
    {
        newPostion = TimeSpan.FromSeconds(e.NewValue);
    }
}

public void CurrentTimeSlider_GetFocus(object sender, RoutedEventArgs e)
{
    MusicState.Instance.OnDrag = true;
}

public void CurrentTimeSlider_LostFocus(object sender, RoutedEventArgs e)
{
    MusicState.Instance.OnDrag = false;
}
Mustafa Özçetin
  • 1,893
  • 1
  • 14
  • 16
Miaoyww
  • 23
  • 3
  • How specifically are you updating the slider, and can this cause any computation to be triggered? Setting the slider value from a timer should not affect the focused control, and anything running on the UI thread should be fast enough to not interrupt the user. Whenever doing any kind of two way updates, you may also need some mechanism to prevent unintended recursion. – JonasH Aug 04 '23 at 12:43
  • before updating the slider value automatically check if the sliders MouseOver or mousedown Property is true and only update the value if that is not the case – Denis Schaf Aug 04 '23 at 13:06
  • No, the value of the silder is be updated by bind Value="{x:Bind ViewModel.MusicState.Position, Mode=TwoWay}" And no any other computation – Miaoyww Aug 04 '23 at 13:43

1 Answers1

1

Let me show you one way to do this by getting the Thumb "HorizontalThumb" (or "VerticalThumb" if the Orientation is Vertical) control. You can find the Thumb control in the DefaultSliderStyle in the Generic.xaml file.

First, you need to install these NuGet packages:

  • CommunityToolkit.WinUI.UI We use this to get the Thumb control.
  • CommunityToolkit.Mvvm We use this to implement the MVVM pattern.

MainPage.xaml

<Page
    x:Class="SliderExample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid ColumnDefinitions="Auto,*">
        <StackPanel
            Grid.Column="0"
            Orientation="Horizontal">
            <Button
                Command="{x:Bind ViewModel.StartIncrementingCommand}"
                Content="Start timer" />
            <Button
                Command="{x:Bind ViewModel.StartIncrementingCancelCommand}"
                Content="Stop timer" />
        </StackPanel>
        <Slider
            x:Name="SliderControl"
            Grid.Column="1"
            VerticalAlignment="Center"
            Maximum="100"
            Minimum="0"
            Value="{x:Bind ViewModel.SliderValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>

</Page>

MainPage.xaml.cs

using CommunityToolkit.WinUI.UI;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using System.Linq;

namespace SliderExample;

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        InitializeComponent();
        this.SliderControl.Loaded += SliderControl_Loaded;
    }

    public MainPageViewModel ViewModel { get; } = new();

    private void SliderControl_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
    {
        if (this.SliderControl
            .FindDescendants()
            .OfType<Thumb>()
            .FirstOrDefault(x => x.Name == "HorizontalThumb") is not Thumb thumb)
        {
            return;
        }

        thumb.PointerEntered += Thumb_PointerEntered;
        thumb.PointerExited += Thumb_PointerExited;
    }

    private void Thumb_PointerExited(object sender, PointerRoutedEventArgs e)
    {
        ViewModel.PauseRequested = false;
    }

    private void Thumb_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        ViewModel.PauseRequested = true;
    }
}

MainPageViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace SliderExample;

public partial class MainPageViewModel : ObservableObject
{
    private readonly PeriodicTimer _timer = new(TimeSpan.FromSeconds(1));

    [ObservableProperty]
    private double _sliderValue = 0;

    [ObservableProperty]
    private bool pauseRequested = false;

    [RelayCommand(IncludeCancelCommand = true)]
    private Task StartIncrementing(CancellationToken cancellationToken)
    {
        return IncrementSlider(cancellationToken);
    }

    private async Task IncrementSlider(CancellationToken cancellationToken)
    {
        try
        {
            while (await _timer.WaitForNextTickAsync(cancellationToken) is true)
            {
                if (PauseRequested is true)
                {
                    continue;
                }

                SliderValue += 1;
            }
        }
        catch (OperationCanceledException)
        {
        }
    }
}
Andrew KeepCoding
  • 7,040
  • 2
  • 14
  • 21