UWP slider After sliding
Asked Answered
E

4

6

I am using a slider to control the position of a playing MediaElement. I have implemented the ValueChanged event to synchronize the position and the value of slider. So when I am dragging on the Thumb, the position of MediaElement also changes. However, this creates bad user experience. The ideal case should be like, after user finishes sliding, MediaElement jumps to that final position. So I am looking for events/handlers like AfterSliding.

I have seen solutions like using OnThumbDragCompleted. However, those questions are about WPF and I cannot find such in UWP. Does anyone have a workaround?

Current:

    private void ProgressBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        MediaPlayer.Position = TimeSpan.FromSeconds(e.NewValue);
        LeftTimeTextBlock.Text = MusicDurationConverter.ToTime((int)e.NewValue);
    }

Expected:

    private void ProgressBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        if (!isDragging || slidingFinished) MediaPlayer.Position = TimeSpan.FromSeconds(e.NewValue);
        LeftTimeTextBlock.Text = MusicDurationConverter.ToTime((int)e.NewValue);
    }

---Update---

I have a timer that ticks every second, but when I tap on the slider (not the thumb) area, the tapped event is not always fired so the thumb will go back and forth between the clicked place and its new position. How can I resolve it so that when user is still holding the thumb, it stays put at the clicked position? I guess the tapped event maybe is not what I should use.

    public void Tick()
    {
        if (ShouldUpdate && !SliderClicked)
        {
            MediaSlider.Value = MediaControl.Player.PlaybackSession.Position.TotalSeconds;
        }
        SliderClicked = false;
    }

    private void MediaSlider_Tapped(object sender, TappedRoutedEventArgs e)
    {
        Debug.WriteLine("Tapped");
        MediaControl.SetPosition(MediaSlider.Value);
        SliderClicked = true;
    }

    private void MediaSlider_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
    {
        MediaControl.SetPosition(MediaSlider.Value);
        ShouldUpdate = true;
        Debug.WriteLine("Completed");
    }

    private void MediaSlider_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
    {
        ShouldUpdate = false;
        Debug.WriteLine("Started");
    }
Ese answered 10/8, 2019 at 10:22 Comment(2)
please provide your code to see how we can help to solve your problemFowlkes
@sayahimad I am not sure what code to put because I am just looking for some events rather than debugging my mode.Ese
R
4

Slider has ManipulationCompleted event.Occurs when a manipulation on the UIElement is complete.You can subscribe it to listen if Slider is completed.At the same time,you need to set the ManipulationMode to a value other than System or None if want to handle manipulation events.

in XAML:

<Slider Name="timelineSlider" ManipulationCompleted="Slider_ManipulationCompleted" ManipulationMode="All" ValueChanged="SeekToMediaPosition" />

in C#:

private void Slider_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
        {​
            Slider MySlider = sender as Slider;​
            myMediaElement.Position = TimeSpan.FromSeconds(MySlider.Value);​
            LeftTimeTextBlock.Text = MusicDurationConverter.ToTime((int)e.NewValue);​
        }

Update:

You can subscribe ValueChanged and ManipulationStarting event.I find when you drag the Thumb,it will first trigger the starting event and then valuechanged.If you only click,it will first trigger the valuechange and then starting event.

in XAML:

<Slider Name="timelineSlider" ManipulationStarting="TimelineSlider_ManipulationStarting"  ManipulationCompleted="Slider_ManipulationCompleted" ManipulationMode="All" ValueChanged="SeekToMediaPosition" Width="70"/>

in C#:

You can declare a oldValue property to save the value of Slider.When trigger the starting event,you can compare oldValue and the current value.If equal,you drag the Thumb.

private double oldValue;

private void SeekToMediaPosition(object sender, RangeBaseValueChangedEventArgs e)
        {
            if (isTap==true)
            {
                ...//tap
            }
            else
            {

            }
            oldValue = timelineSlider.Value;
        }
private void Slider_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
        {

            Slider MySlider = sender as Slider;
            double s = MySlider.Value;
            isTap = true;
        }

        private void TimelineSlider_ManipulationStarting(object sender, ManipulationStartingRoutedEventArgs e)
        {
            if (oldValue == timelineSlider.Value) {
                //tuozhuai
                isTap = false;
            }
            else{
                isTap = true;
            }
        }
Regnant answered 12/8, 2019 at 7:13 Comment(6)
Thank you! That's exactly what I am looking for!Ese
I have one more question. How do you tell whether it's a drag event or a click event getting triggered? The click event doesn't seem to have the ManipulationCompleted fired.Ese
If you want to judge a click event,you can add a Tapped event like:<Slider IsTapEnabled="True" Tapped="Slider_Tapped" ManipulationCompleted="Slider_ManipulationCompleted" .....>.When you click,the event will be triggered.You can do the same operation like ManipulationCompleted does.Regnant
Sorry to bother you again but I have some conflicts that I don't know how to fix. Could you please take at look at the update area?Ese
I also found this problem, I have updated the answer, just a thought or suggestion。Regnant
Thank you dalao! tuozhuai xswl. But I don't think your solution suits my case well, as I am actually not distinguishing between tapping and dragging, but trying to stop the slider from updating its value when user is still holding/tapping the thumb (and on the first click the thumb will flash back between the next-second position and the clicked position). But thank you anyway for your patience and suggestion!Ese
C
2

Nothing works better than the ValueChanged event. It just works too well and is called way too frequently. In these cases, I have a utility method that takes a code block and runs it when the user has made a decision:

public static class Utility
{
    private static DispatcherTimer settledTimer;
    public delegate void SettledCallback( );
    public static void RunWhenSettled(SettledCallback callback)
    {
        if (settledTimer == null)
        {
            settledTimer = new DispatcherTimer( );
            settledTimer.Interval = TimeSpan.FromSeconds(.4);
            settledTimer.Tick += delegate
            {
                settledTimer.Stop( );
                callback( );
                settledTimer = null;
            };
            settledTimer.Start( );
        }
        else if (settledTimer.IsEnabled)
            settledTimer.Start( );   // resets the timer...
    }
}

private void ProgressBar_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    Utility.RunWhenSettled(delegate
    {
        MediaPlayer.Position = TimeSpan.FromSeconds(e.NewValue);
        LeftTimeTextBlock.Text = MusicDurationConverter.ToTime((int)e.NewValue);
    });
}
Cerecloth answered 16/5, 2020 at 14:49 Comment(0)
G
1

Add event handlers for ManipulationStarted and ManipulationCompleted. Don't forget to set ManipulationMode also. E.g. ManipulationMode="All".

    private bool dragStarted = false;

    private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        if (!dragStarted)
        {
            // Do work
        }
    }

    private void Slider_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
    {
        dragStarted = true;
    }

    private void Slider_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
    {
        // Do work
        dragStarted = false;
    }
Goodfornothing answered 29/7, 2020 at 11:4 Comment(0)
S
0

I currently have found a better solution, as events like ManipulationCompleted won't get called steadily, when the music is playing and results in the user and the timer moving the slider at the same time.

The basic idea is introducing a bool TriggeredByTimer which avoids the timer to get entangled with user's dragging.

private TriggeredByTimer = false, wasPlaying = false;
public MediaControl()
{
    this.InitializeComponent();
    SliderTimer.Tick += (s, args) =>
    {
        if (MusicPlayer.IsPlaying)
        {
            TriggeredByTimer = true;
            MediaSlider.Value = MusicPlayer.Position;
            TriggeredByTimer = false;
        }
    };
    SliderTimer.Start();
}

private void MediaSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    double newValue = e.NewValue;
    if (TriggeredByTimer)
    {
    }
    else
    {
        if (newValue <= MediaSlider.Maximum)
        {
            MusicPlayer.Position = newValue;
        }
    }
    if (LeftTimeTextBlock != null) LeftTimeTextBlock.Text = MusicDurationConverter.ToTime(newValue);
}

// The manipulation event listeners are now optional. 
// I choose to keep them because they could provide a potentially better user experience, 
// as users won't get the noise of fast-forwarding when dragging.
private void MediaSlider_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
    if (wasPlaying)
    {
        MusicPlayer.Play();
    }
}

private void MediaSlider_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
    wasPlaying = MusicPlayer.IsPlaying;
    MusicPlayer.Pause();
}
Schweinfurt answered 30/4, 2024 at 8:51 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.