How can I detect when the XAML Slider is Completed?
Asked Answered
K

4

6

In XAML I have the <Slider />. It has the ValueChanged event. This event fires with every change to Value. I need to detect when the value change is over. LostFocus, PointerReleased are not the correct event. How can I detect this?

Kohn answered 13/12, 2012 at 20:28 Comment(4)
Maybe a timer attached to ValueChanged?Kohn
It sounds like what you're struggling with is defining when exactly you want to receive this event. I think if you figure that out then you will probably figure out how to send it. Maybe a slider with discrete value options would make that more clear?Kirtle
Did you ever fix this problem?Glacis
This seems to be the issue since the WPF/Silverlight. However, if you think about it, how do you know when the value change is really over? You can't really know unless you set a specific duration that you will be certain that a user is done with the slider. So I think the best way is to use DispatcherTimer like what Tim Bourguignon did.Peaceable
T
7

You can create a new class and inherit from Slider. From there on, you can look for the Thumb control & listen for the events you want.

Something like this should work:

public class SliderValueChangeCompletedEventArgs : RoutedEventArgs
{
    private readonly double _value;

    public double Value { get { return _value; } }

    public SliderValueChangeCompletedEventArgs(double value)
    {
        _value = value;
    }
}
public delegate void SlideValueChangeCompletedEventHandler(object sender, SliderValueChangeCompletedEventArgs args);

public class ExtendedSlider : Slider
{
    public event SlideValueChangeCompletedEventHandler ValueChangeCompleted;
    private bool _dragging = false;

    protected void OnValueChangeCompleted(double value)
    {
        if (ValueChangeCompleted != null)
        {
            ValueChangeCompleted(this, new SliderValueChangeCompletedEventArgs(value) );
        }
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var thumb = base.GetTemplateChild("HorizontalThumb") as Thumb;
        if (thumb != null)
        {
            thumb.DragStarted += ThumbOnDragStarted;
            thumb.DragCompleted += ThumbOnDragCompleted;
        }
        thumb = base.GetTemplateChild("VerticalThumb") as Thumb;
        if (thumb != null)
        {
            thumb.DragStarted += ThumbOnDragStarted;
            thumb.DragCompleted += ThumbOnDragCompleted;
        }
    }

    private void ThumbOnDragCompleted(object sender, DragCompletedEventArgs e)
    {
        _dragging = false;
        OnValueChangeCompleted(this.Value);
    }

    private void ThumbOnDragStarted(object sender, DragStartedEventArgs e)
    {
        _dragging = true;
    }

    protected override void OnValueChanged(double oldValue, double newValue)
    {
        base.OnValueChanged(oldValue, newValue);
        if (!_dragging)
        {
            OnValueChangeCompleted(newValue);
        }
    }
}
Tetrapterous answered 6/11, 2014 at 17:1 Comment(0)
L
10

XAML, WinRT, Windows8.1 and UWP:

PointerCaptureLost event should work for mouse / touch
KeyUp event for keyboard

Leschen answered 16/6, 2015 at 9:12 Comment(0)
T
7

You can create a new class and inherit from Slider. From there on, you can look for the Thumb control & listen for the events you want.

Something like this should work:

public class SliderValueChangeCompletedEventArgs : RoutedEventArgs
{
    private readonly double _value;

    public double Value { get { return _value; } }

    public SliderValueChangeCompletedEventArgs(double value)
    {
        _value = value;
    }
}
public delegate void SlideValueChangeCompletedEventHandler(object sender, SliderValueChangeCompletedEventArgs args);

public class ExtendedSlider : Slider
{
    public event SlideValueChangeCompletedEventHandler ValueChangeCompleted;
    private bool _dragging = false;

    protected void OnValueChangeCompleted(double value)
    {
        if (ValueChangeCompleted != null)
        {
            ValueChangeCompleted(this, new SliderValueChangeCompletedEventArgs(value) );
        }
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var thumb = base.GetTemplateChild("HorizontalThumb") as Thumb;
        if (thumb != null)
        {
            thumb.DragStarted += ThumbOnDragStarted;
            thumb.DragCompleted += ThumbOnDragCompleted;
        }
        thumb = base.GetTemplateChild("VerticalThumb") as Thumb;
        if (thumb != null)
        {
            thumb.DragStarted += ThumbOnDragStarted;
            thumb.DragCompleted += ThumbOnDragCompleted;
        }
    }

    private void ThumbOnDragCompleted(object sender, DragCompletedEventArgs e)
    {
        _dragging = false;
        OnValueChangeCompleted(this.Value);
    }

    private void ThumbOnDragStarted(object sender, DragStartedEventArgs e)
    {
        _dragging = true;
    }

    protected override void OnValueChanged(double oldValue, double newValue)
    {
        base.OnValueChanged(oldValue, newValue);
        if (!_dragging)
        {
            OnValueChangeCompleted(newValue);
        }
    }
}
Tetrapterous answered 6/11, 2014 at 17:1 Comment(0)
A
2

You can use pair of bool values isValueChanged and (if possible change value without manipulation of pointer ) isPressed;

private void Slider_ValueChanged(object s, RangeBaseValueChangedEventArgs e) {
    if (!isPressed) {
        AcceptChanges();
    } else {
        isValueChanged = true;
    }
}

Initialization code:

Window.Current.CoreWindow.PointerPressed += (e, a) => { isPressed = true; };

Window.Current.CoreWindow.PointerReleased += (e, a) => {
    isPressed = false;
    if (isValueChanged) AcceptChanges();
};
Aether answered 13/8, 2013 at 9:2 Comment(1)
Not sure this will support keyboard.Kohn
H
1

I had a similar issue using a Slider on Windows8/WinRT.

My problem was the following: I was reacting to the ValueChanged Event and performing a long lasting operation (writing asynchronously to a file) after each trigger. And thus running into a concurrent editing exception. In order to avoid this, I used a DispatcherTimer.

    //Class member
    private DispatcherTimer myDispatcherTimer = null;

    private void OnSliderValueChanged(object sender, RangeBaseValueChangedEventArgs e)
    {
        //I update my UI right away
        ...

        //If the dispatcher is already created, stop it
        if (myDispatcherTimer!= null)
            myDispatcherTimer.Stop();

        //Overwrite the DispatcherTimer and thus reset the countdown
        myDispatcherTimer= new DispatcherTimer();
        myDispatcherTimer.Tick += (sender, o) => DoSomethingAsync();
        myDispatcherTimer.Interval = new TimeSpan(0,0,2);
        myDispatcherTimer.Start();
    }

    private async void DoSomethingAsync()
    {
        await DoThatLongSaveOperation();
    }

You cannot directly detect what the final value is, but you can at least delay the operation until there is a long pause between two updates (e.g. in my case, if the user drags the slider and stops while maintaining the drag for 2 seconds, the save operation will be fired anyway).

Harkness answered 12/3, 2013 at 10:37 Comment(1)
Ack! A timer. 2 seconds? Why not 1. 1 second, why not 3? I think I would prefer explicit events. What if their system is slow? What if their system is fast? You know what I mean.Kohn

© 2022 - 2024 — McMap. All rights reserved.