3

you might say it's a duplicate of How to stop update value of slider while dragging it? but honestly the solution there is from 2011 and just not working for me (as even commented on the accepted answer by other users), and the other answers aren't relevant for me. So please be patient guys.

That's my problem: I have a slider that is binded to a value like that:

<Slider Name="AudioSlider" Value="{Binding AudioValue}" 
        Grid.Column="1"    Grid.Row="1"    Height="30" 
        SmallChange="1"    Maximum="100" 
        IsSnapToTickEnabled="True"                                      
        IsMoveToPointEnabled="True"/>

And this is the value:

        private double _audioValue;
        public double AudioValue
        {
            get { return _audioValue; }
            set
            {
                if (_audioValue == value)
                    return;
                _audioValue = value;
                SendPacket(cockpitType, (byte)Index.AudioSlider, (byte)_audioValue);
                OnPropertyChanged("AudioValue");
            }
        }

As you can see, every time the value changes I am sending a packet, when it updates the value on drag it's just spamming the value on the server and it's something that must be prevented.

I have tried to bind the slider to a function (mouseUp) but I couldn't get the value this way.

Any ideas what I can do?

Thanks a lot.

Edit:

The answer here WPF: Slider with an event that triggers after a user drags doesn't help me since it's a generated slider inside a data template, events aren't relevant for me.

Community
  • 1
  • 1
Guy Ben-Moshe
  • 874
  • 1
  • 15
  • 23
  • 1
    "the other answers aren't relevant for me". That's probably because you did't take a closer look. The [derived Slider with FinalValue property](http://stackoverflow.com/a/20430288/1136211) works perfectly if you add `FrameworkPropertyMetadataOptions.BindsTwoWayByDefault` to the dependency property metadata. – Clemens Jan 10 '16 at 09:19

4 Answers4

18

If you are using WPF 4.5 you can just use

Value="{Binding AudioValue, Delay=500}"

which will throttle the change notifications. If the user drags the slider quickly this will "reset" an internal timer and will drop the pending notifications. When the user stops and the 500 ms delay is over, it will call your setter method once.

There are lots of ways of doing this, so this is just one suggestion. You could do this with Rx and its Throttle method in the VM. That would be essentially the same as the Binding's Delay property.

I have a problem with overriding only the OnThumbDragCompleted method as suggested in other answers. That means the change would only propagate if the Slider was manipulated by dragging the thumb. If you click next to the thumb for example, your property wouldn't get notified. Basically what I'm saying is that you have to be sure to synchronize the value of Value and FinalValue if you are going with a solution where you derive from Slider.

Szabolcs Dézsi
  • 8,743
  • 21
  • 29
  • Are you sure it should work? I am using WPF 4.5 and it says "Error 1 The property 'Delay' was not found in type 'Binding'." – Guy Ben-Moshe Jan 10 '16 at 09:41
  • @GuyBen-Moshe it should work. It needs .NET Framework 4.5 or above I think, and as I recall from the comments in [this answer](http://stackoverflow.com/a/34651250/68972) you were not using it. – Jcl Jan 10 '16 at 09:44
  • I was notified about an upgrade to the WPF (since I requested it from my boss after that answer), I will look into it, thanks :) – Guy Ben-Moshe Jan 10 '16 at 09:49
2

What I suggest is to create your own slider inheriting from the original one, which will clear the binding on drag start and restore it once the drag is completed. Here's an example:

public class CustomSlider : Slider
{
    private Binding SupressedBinding { get; set; }

    protected override void OnThumbDragStarted(DragStartedEventArgs e)
    {
        base.OnThumbDragStarted(e);
        var expression = BindingOperations.GetBindingExpression(this, ValueProperty);
        if (expression != null)
        {
            SupressedBinding = expression.ParentBinding;
            //clearing the binding will cause the Value to reset to default,
            //so we'll need to restore it
            var value = Value;
            BindingOperations.ClearBinding(this, ValueProperty);
            SetValue(ValueProperty, value);
        }
    }

    protected override void OnThumbDragCompleted(DragCompletedEventArgs e)
    {
        if (SupressedBinding != null)
        {
            //again, restoring the binding will cause the Value to update to source's
            //value (which is "out of date" by now), so we'll need to restore it
            var value = Value;
            BindingOperations.SetBinding(this, ValueProperty, SupressedBinding);
            SetCurrentValue(ValueProperty, value);
            SupressedBinding = null;
        }
        base.OnThumbDragCompleted(e);
    }
}
Grx70
  • 10,041
  • 1
  • 40
  • 55
  • As @Clemes noted in the comments, [this answer](http://stackoverflow.com/a/20430288/1136211) would prove even more useful. Just have a different `FinalValue` which only changes on `OnThumbDragCompleted` – Jcl Jan 10 '16 at 09:28
  • 1
    @Jcl I don't agree. The other answer is missing crucial (in my opinion) functionality - see my comment there. – Grx70 Jan 10 '16 at 09:37
  • @Grx70 a matter of setting `Value` when `FinalValue` changes (with a `PropertyChangedCallback` on the dp), still better than this, in my oppinion, but you are right, would need some tweaks. I usually never take SO answers as final, just hints on what to do :-) – Jcl Jan 10 '16 at 09:40
  • @Jcl That's true, and I agree that the other solution is more versatile in general case, so I'm not even saying this solution is better. It just takes a different approach on the problem. – Grx70 Jan 10 '16 at 09:49
1

Create a boolean property that you set to false on mousedown and to true on mouseup, and in your property, send the packet only if that property is true ?

Noctis
  • 11,507
  • 3
  • 43
  • 82
  • The code behind is happening in a different class (not the window class), since it's binded with a data template, is it still possible to do that? – Guy Ben-Moshe Jan 10 '16 at 09:06
  • 1
    @GuyBen-Moshe nothing prevents you to add the property to your viewmodel, and do something like `((MyViewModel)AudioSlider.DataContext).SendPackets = true;` on mousedown, or something – Jcl Jan 10 '16 at 09:08
0

I completely agree with Noctis, in addition you can use behavior which get the Action<bool>, and using that action and mousedown/mouseup events can signalize if you can send the packet. Moreover you can handle the final value on mouseup, pass that value to the viewmodel, update the AudioValue indicator and send the packet you need (you need another action in behavior, something like Action<bool, double>, where boolean signalize if you can sent packet and double is for the AudioValue itself).

I'll glad to help, if will need help with the code. Regards.

Ilan
  • 2,762
  • 1
  • 13
  • 24