Как отключить событие изменения значения ползунка?

У меня есть слайдер, который при изменении значения приводит к довольно серьезному вычислению, поэтому я хочу отключить его, чтобы запустить реальное событие после того, как, например, 50 мс пройдет, когда пользователь закончит его сбрасывать.

В то время как я узнал несколько разных вещей о Rx, неясно, как я должен подходить к этому с использованием шаблона MVVM.

В моем текущем подходе MVVM я получил значение слайдера, привязанное к моей модели viewModel. Я бы предпочел добавить Rx-дроссель с минимальным возможным воздействием на существующий код (как минимум на начало).

Я видел некоторые другие темы о MVVM и Rx, и я не думаю, что они привели меня к определенному направлению с моей проблемой. Я вижу различные возможные подходы и хотел бы не изобретать супруг.

Ответы

Ответ 1

В этом случае вам нужно привязать к событию PropertyChanged вашего ViewModel, что-то вроде:

Observable.FromEvent<PropertyChangedEventArgs>(x => this.PropertyChanged +=x, x => this.PropertyChanged -= x)
    .Where(x => x.PropertyName == "SliderName")
    .Select(_ => this.SliderName)
    .Throttle(TimeSpan.FromMilliseconds(50));

Или, если вы использовали ReactiveUI, он выглядел бы так:

this.WhenAnyValue(x => x.SliderName)
    .Throttle(TimeSpan.FromMilliseconds(50), RxApp.DeferredScheduler);

Ответ 2

Давайте рассмотрим только проблему. У вас есть модель просмотра, у которой есть определенное свойство double. Когда значение присваивается этому свойству, имеет место довольно дорогостоящий расчет. Обычно это не проблема, но когда пользовательский интерфейс связывает значение Slider с этим свойством, сгенерированные быстрые изменения создают проблему.

Первое решение, которое должно быть принято, - это точка зрения и модель представления, которая отвечает за решение этой проблемы. С другой стороны, можно было бы утверждать, что "Модель-образец" выбрала "свойство", чтобы присвоить присвоение свойства операции с затратами, с другой стороны, "Вид" "выбрал" для присвоения свойства с помощью Slider.

Мой выбор будет в стороне от вещей, потому что это лучшее место для реализации этого. Однако, вместо того, чтобы играть с View непосредственно, я бы добавил новый Control, чтобы добавить эту функцию. Позвольте называть его DelaySlider. Он будет получен из Silder и имеет два дополнительных свойства зависимостей Delay и DelayedValue. DelayedValue будет соответствовать существующему значению свойства Value, но только после того, как прошло Delay миллисекунды с момента последнего изменения Value.

Вот полный код для управления: -

public class DelaySlider : Slider
{
    private DispatcherTimer myTimer;

    private bool myChanging = false;

    #region public double DelayedValue
    public double DelayedValue
    {
        get { return (double)GetValue(DelayedValueProperty); }
        set { SetValue(DelayedValueProperty, value); }
    }

    public static readonly DependencyProperty DelayedValueProperty =
        DependencyProperty.Register(
            "DelayedValue",
            typeof(double),
            typeof(DelaySlider),
            new PropertyMetadata(0.0, OnDelayedValuePropertyChanged));

    private static void OnDelayedValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DelaySlider source = d as DelaySlider;
        if (source != null && !source.myChanging)
        {
            source.Value = (double)e.NewValue;
        }
    }
    #endregion public double DelayedValue

    #region public int Delay

    public int Delay
    {
        get { return (int)GetValue(DelayProperty); }
        set { SetValue(DelayProperty, value); }
    }

    public static readonly DependencyProperty DelayProperty =
        DependencyProperty.Register(
            "Delay",
            typeof(int),
            typeof(DelaySlider),
            new PropertyMetadata(0, OnDelayPropertyChanged));

    private static void OnDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        DelaySlider source = d as DelaySlider;
        if (source != null)
        {
            source.OnDelayPropertyChanged((int)e.OldValue, (int)e.NewValue);
        }
    }

    private void OnDelayPropertyChanged(int oldValue, int newValue)
    {
        if (myTimer != null)
        {
            myTimer.Stop();
            myTimer = null;
        }

        if (newValue > 0)
        {
            myTimer = new DispatcherTimer();
            myTimer.Tick += myTimer_Tick;

            myTimer.Interval = TimeSpan.FromMilliseconds(newValue);
        }
    }

    void myTimer_Tick(object sender, EventArgs e)
    {
        myTimer.Stop();
        myChanging = true;
        SetValue(DelayedValueProperty, Value);
        myChanging = false;
    }
    #endregion public int Delay


    protected override void OnValueChanged(double oldValue, double newValue)
    {
        base.OnValueChanged(oldValue, newValue);
        if (myTimer != null)
        {
            myTimer.Start();
        }

    }
}

Теперь замените ваш Silder на DelaySlider и привяжите свойство View-Model к DelayedValue и укажите значение миллисекундной задержки в свойстве Delay.

Теперь у вас есть полезный повторно используемый элемент управления, который вы не перепутали с неприятными трюками в представлении, у вас нет дополнительного кода в кодовом конце представления, модель View не изменяется и не изменяется, и вы не нужно было включать в себя материал Rx вообще.