WPF:具有在用户拖动后触发的事件的滑块

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF:具有在用户拖动后触发的事件的滑块相关的知识,希望对你有一定的参考价值。

我目前在WPF中制作MP3播放器,我想制作一个滑块,允许用户通过向左或向右滑动滑块来寻找MP3中的特定位置。

我已经尝试过使用ValueChanged事件,但是每当它的值被改变时触发,所以如果你拖动它,事件会多次触发,我希望事件只在用户完成拖动滑块时触发然后得到新的价值。

我怎样才能做到这一点?


[更新]

我在MSDN上找到了this post,基本上讨论了同样的事情,他们提出了两个“解决方案”;要么继承Slider,要么在DispatcherTimer事件中调用ValueChanged,该事件在一个时间跨度后调用该动作。

你能想出比上面提到的更好的东西吗?

答案

你可以使用拇指的'DragCompleted'事件。不幸的是,这仅在拖动时触发,因此您需要单独处理其他点击和按键。如果您只希望它可拖动,则可以通过将LargeChange设置为0并将Focusable设置为false来禁用这些移动滑块的方法。

例:

<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
另一答案

Slider的这个子类版本可以根据需要使用:

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();
    }
}
另一答案

除了使用Thumb.DragCompleted事件,您还可以同时使用ValueChangedThumb.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);
}
另一答案
<Slider PreviewMouseUp="MySlider_DragCompleted" />

适合我。

您想要的值是mousup事件之后的值,无论是侧面点击还是拖动句柄后。

由于MouseUp没有隧道传输(它可以在它之前处理),你必须使用预览MouseUp。

另一答案

另一个MVVM友好的解决方案(我对答案不满意)

视图:

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

视图模型:

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可以选择对每个值的变化做出反应,或者在用户操作结束时做出反应(这增加了大量的灵活性,特别是在使用绑定时)。

另一答案

这是一个处理此问题的行为加上键盘相同的行为。 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>
另一答案

我的解决方案基本上是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()
        }
    }

希望有人帮助!

另一答案

如果您想获取操作结束信息,即使用户没有使用拇指更改值(即单击轨迹栏中的某个位置),您也可以将事件处理程序附加到滑块以获取按下的指针并捕获丢失的事件。你可以为键盘事件做同样的事情

var pointerPressedHandler   = new

以上是关于WPF:具有在用户拖动后触发的事件的滑块的主要内容,如果未能解决你的问题,请参考以下文章

WPF 滑块仅可拖动

反应:带有自定义钩子的滑块无法正常工作

如何拖动元素并确保光标不会比它快?

iOS 自由拖动的滑块

防止拖动时触发点击事件[重复]

wpf 的 slider控件问题