Подтвердить что ты не робот

WPF: слайдер с событием, которое запускается после того, как пользователь перетаскивает

В настоящее время я создаю MP3-плеер в WPF, и я хочу создать слайдер, который позволит пользователю искать определенную позицию в MP3, сдвигая ползунок влево или вправо.

Я попытался использовать событие ValueChanged, но это срабатывает при каждом изменении значения, поэтому, если вы перетащите его, событие будет срабатывать несколько раз, Я хочу, чтобы событие срабатывало только тогда, когда пользователь закончив перетаскивание ползунка, а затем получим новое значение.

Как я могу это достичь?


[Обновление]

Я нашел этот пост на MSDN, который в основном обсуждает одно и то же, и они придумали два "решения"; либо подклассифицируя Slider, либо вызывая a DispatcherTimer в событии ValueChanged, которое вызывает действие после периода времени.

Можете ли вы придумать что-нибудь лучше, чем два упомянутых выше?

4b9b3361

Ответ 1

Для этого вы можете использовать для этого событие "DragCompleted" большого пальца. К сожалению, это происходит только при перетаскивании, поэтому вам придется обрабатывать другие нажатия и нажатия клавиш отдельно. Если вы хотите, чтобы его можно было перетаскивать, вы можете отключить эти средства перемещения ползунка, установив для параметра LargeChange значение 0 и Focusable в false.

Пример:

<Slider Thumb.DragCompleted="MySlider_DragCompleted" />

Ответ 2

Кроме использования события Thumb.DragCompleted вы также можете использовать как ValueChanged, так и Thumb.DragStarted, таким образом вы не теряете функциональность, когда пользователь изменяет значение, нажимая клавиши со стрелками или щелкая по ползунку.

Xaml:

<Slider ValueChanged="Slider_ValueChanged"
    Thumb.DragStarted="Slider_DragStarted"
    Thumb.DragCompleted="Slider_DragCompleted"/>

Код позади:

private bool dragStarted = false;

private void Slider_DragCompleted(object sender, DragCompletedEventArgs e)
{
    DoWork(((Slider)sender).Value);
    this.dragStarted = false;
}

private void Slider_DragStarted(object sender, DragStartedEventArgs e)
{
    this.dragStarted = true;
}

private void Slider_ValueChanged(
    object sender,
    RoutedPropertyChangedEventArgs<double> e)
{
    if (!dragStarted)
        DoWork(e.NewValue);
}

Ответ 3

<Slider PreviewMouseUp="MySlider_DragCompleted" />

работает для меня.

Значение, которое вы хотите, - это значение после события mousup, либо на кликах сбоку, либо после перетаскивания дескриптора.

Так как MouseUp не туннелируется (до тех пор пока он не используется), вы должны использовать PreviewMouseUp.

Ответ 4

Другое решение, совместимое с MVVM (я не был доволен ответами)

Вид:

<Slider Maximum="100" Value="{Binding SomeValue}"/>

ViewModel:

public class SomeViewModel : INotifyPropertyChanged
{
    private readonly object _someValueLock = new object();
    private int _someValue;
    public int SomeValue
    {
        get { return _someValue; }
        set
        {
            _someValue = value;
            OnPropertyChanged();
            lock (_someValueLock)
                Monitor.PulseAll(_someValueLock);
            Task.Run(() =>
            {
                lock (_someValueLock)
                    if (!Monitor.Wait(_someValueLock, 1000))
                    {
                        // do something here
                    }
            });
        }
    }
}

Задержка (в 1000 ms в данном примере). Новая задача создается для каждого изменения, выполняемого слайдером (либо с помощью мыши, либо с клавиатуры). Перед запуском задачи он сигнализирует (используя Monitor.PulseAll, возможно, даже Monitor.Pulse будет достаточно) для запуска уже задач (если они есть) для остановки. Что-то происходит, когда Monitor.Wait не получает сигнал в течение таймаута.

Почему это решение? Мне не нравится поведение нереста или ненужное управление событиями в представлении. Весь код находится в одном месте, никаких дополнительных событий не требуется, ViewModel имеет возможность либо реагировать на каждое изменение значения, либо в конце работы пользователя (что добавляет тонны гибкости, особенно при использовании привязки).

Ответ 5

Вот поведение, которое справляется с этой проблемой, плюс то же самое с клавиатурой. https://gist.github.com/4326429

Он предоставляет свойства Command и Value. Значение передается как параметр команды. Вы можете привязать данные к свойству value (и использовать его в viewmodel). Вы можете добавить обработчик событий для подхода, основанного на кодировании.

<Slider>
  <i:Interaction.Behaviors>
    <b:SliderValueChangedBehavior Command="{Binding ValueChangedCommand}"
                                  Value="{Binding MyValue}" />
  </i:Interaction.Behaviors>
</Slider>

Ответ 6

<Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider>

PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture);
PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted));

Ответ 7

Мое решение - это в основном решение Santo с еще несколькими флагами. Для меня ползунок обновляется от чтения потока или манипуляции с пользователем (либо с помощью перетаскивания мышью, либо с помощью клавиш со стрелками и т.д.)

Во-первых, я написал код, чтобы обновить значение ползунка от чтения потока:

    delegate void UpdateSliderPositionDelegate();
    void UpdateSliderPosition()
    {
        if (Thread.CurrentThread != Dispatcher.Thread)
        {
            UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
            Dispatcher.Invoke(function, new object[] { });
        }
        else
        {
            double percentage = 0;  //calculate percentage
            percentage *= 100;

            slider.Value = percentage;  //this triggers the slider.ValueChanged event
        }
    }

Затем я добавил свой код, который был захвачен, когда пользователь манипулировал слайдером с помощью мыши:

<Slider Name="slider"
        Maximum="100" TickFrequency="10"
        ValueChanged="slider_ValueChanged"
        Thumb.DragStarted="slider_DragStarted"
        Thumb.DragCompleted="slider_DragCompleted">
</Slider>

И добавил код позади:

/// <summary>
/// True when the user is dragging the slider with the mouse
/// </summary>
bool sliderThumbDragging = false;

private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
    sliderThumbDragging = true;
}

private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
    sliderThumbDragging = false;
}

Когда пользователь обновляет значение ползунка с помощью перетаскивания мышью, значение будет по-прежнему изменяться из-за чтения потока и вызова UpdateSliderPosition(). Чтобы предотвратить конфликты, нужно было изменить UpdateSliderPosition():

delegate void UpdateSliderPositionDelegate();
void UpdateSliderPosition()
{
    if (Thread.CurrentThread != Dispatcher.Thread)
    {
        UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
        Dispatcher.Invoke(function, new object[] { });
    }
    else
    {
        if (sliderThumbDragging == false) //ensure user isn't updating the slider
        {
            double percentage = 0;  //calculate percentage
            percentage *= 100;

            slider.Value = percentage;  //this triggers the slider.ValueChanged event
        }
    }
}

В то время как это предотвратит конфликты, мы по-прежнему не можем определить, будет ли значение обновляться пользователем или вызовом UpdateSliderPosition(). Это фиксируется еще одним флагом, на этот раз установленным в пределах UpdateSliderPosition().

    /// <summary>
    /// A value of true indicates that the slider value is being updated due to the stream being read (not by user manipulation).
    /// </summary>
    bool updatingSliderPosition = false;
    delegate void UpdateSliderPositionDelegate();
    void UpdateSliderPosition()
    {
        if (Thread.CurrentThread != Dispatcher.Thread)
        {
            UpdateSliderPositionDelegate function = new UpdateSliderPositionDelegate(UpdateSliderPosition);
            Dispatcher.Invoke(function, new object[] { });
        }
        else
        {
            if (sliderThumbDragging == false) //ensure user isn't updating the slider
            {
                updatingSliderPosition = true;
                double percentage = 0;  //calculate percentage
                percentage *= 100;

                slider.Value = percentage;  //this triggers the slider.ValueChanged event

                updatingSliderPosition = false;
            }
        }
    }

Наконец, мы можем определить, обновляется ли ползунок пользователем или по вызову UpdateSliderPosition():

    private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        if (updatingSliderPosition == false)
        {
            //user is manipulating the slider value (either by keyboard or mouse)
        }
        else
        {
            //slider value is being updated by a call to UpdateSliderPosition()
        }
    }

Надеюсь, что это поможет кому-то!

Ответ 8

Если вы хотите получить информацию о завершении манипуляции, даже если пользователь не использует большой палец для изменения значения (т.е. щелкнув где-то на панели треков), вы можете прикрепить обработчик события к вашему ползунку для нажатия и захвата указателя потерянные события. Вы можете сделать то же самое для событий клавиатуры

var pointerPressedHandler   = new PointerEventHandler(OnSliderPointerPressed);
slider.AddHandler(Control.PointerPressedEvent, pointerPressedHandler, true);

var pointerCaptureLostHandler   = new PointerEventHandler(OnSliderCaptureLost);
slider.AddHandler(Control.PointerCaptureLostEvent, pointerCaptureLostHandler, true);

var keyDownEventHandler = new KeyEventHandler(OnSliderKeyDown);
slider.AddHandler(Control.KeyDownEvent, keyDownEventHandler, true);

var keyUpEventHandler   = new KeyEventHandler(OnSliderKeyUp);
slider.AddHandler(Control.KeyUpEvent, keyUpEventHandler, true);

"Магия" здесь - это AddHandler с истинным параметром в конце, который позволяет нам получать "внутренние" события ползунка. Обработчики событий:

private void OnKeyDown(object sender, KeyRoutedEventArgs args)
{
    m_bIsPressed = true;
}
private void OnKeyUp(object sender, KeyRoutedEventArgs args)
{
    Debug.WriteLine("VALUE AFTER KEY CHANGE {0}", slider.Value);
    m_bIsPressed = false;
}

private void OnSliderCaptureLost(object sender, PointerRoutedEventArgs e)
{
    Debug.WriteLine("VALUE AFTER CHANGE {0}", slider.Value);
    m_bIsPressed = false;
}
private void OnSliderPointerPressed(object sender, PointerRoutedEventArgs e)
{
    m_bIsPressed = true;
}

Член m_bIsPressed будет прав, когда пользователь в настоящее время манипулирует слайдером (щелчок, перетаскивание или клавиатура). Это будет reset false после выполнения.

private void OnValueChanged(object sender, object e)
{
    if(!m_bIsPressed) { // do something }
}

Ответ 9

Эта подклассовая версия слайдера wokrs, как вы хотите:

public class NonRealtimeSlider : Slider
{
    static NonRealtimeSlider()
    {
        var defaultMetadata = ValueProperty.GetMetadata(typeof(TextBox));

        ValueProperty.OverrideMetadata(typeof(NonRealtimeSlider), new FrameworkPropertyMetadata(
        defaultMetadata.DefaultValue,
        FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        defaultMetadata.PropertyChangedCallback,
        defaultMetadata.CoerceValueCallback,
        true,
        UpdateSourceTrigger.Explicit));
    }

    protected override void OnThumbDragCompleted(DragCompletedEventArgs e)
    {
        base.OnThumbDragCompleted(e);
        GetBindingExpression(ValueProperty)?.UpdateSource();
    }
}