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

And*_*ech 52 wpf xaml slider

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

我已尝试使用该ValueChanged事件,但每次更改值时都会触发,因此如果将其拖动,事件将多次触发,我希望事件仅在用户完成拖动滑块时触发然后获取新事件价值.

我怎样才能做到这一点?


[更新]

我在MSDN上发现这篇文章基本上讨论了同样的事情,他们提出了两个"解决方案"; 要么继承Slider,要么在一个时间跨度后调用动作DispatcherTimerValueChanged事件中调用a.

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

Ala*_*lan 70

除了使用Thumb.DragCompleted 事件,您还可以同时使用ValueChangedThumb.DragStarted,这样,当用户通过按下方向键或单击滑动条上修改了价值,你不会失去功能.

XAML:

<Slider ValueChanged="Slider_ValueChanged"
    Thumb.DragStarted="Slider_DragStarted"
    Thumb.DragCompleted="Slider_DragCompleted"/>
Run Code Online (Sandbox Code Playgroud)

代码背后:

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);
}
Run Code Online (Sandbox Code Playgroud)


Yot*_*aXP 51

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

例:

<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
Run Code Online (Sandbox Code Playgroud)

  • 但是要小心,这不会通过按箭头键来处理用户更改值,请参阅santo的帖子以获得解决方案. (5认同)
  • 不幸的是,WinRT没有Thumb等价物 (4认同)

Pet*_*ter 13

<Slider PreviewMouseUp="MySlider_DragCompleted" />
Run Code Online (Sandbox Code Playgroud)

适合我.

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

由于MouseUp没有隧道传输(它可以在它之前进行处理),因此您必须使用PreviewMouseUp.


Sin*_*atr 5

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

视图:

<Slider Maximum="100" Value="{Binding SomeValue}"/>
Run Code Online (Sandbox Code Playgroud)

视图模型:

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
                    }
            });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它被延迟(1000在给定示例中为ms)操作.为滑块(通过鼠标或键盘)完成的每个更改创建新任务.在开始任务之前,它发出信号(通过使用Monitor.PulseAll,甚至Monitor.Pulse可能就足够了)来运行已经完成的任务(如果有的话)来停止.只有在超时内没有获得信号时才会发生某些事情Monitor.Wait.

为什么这个解决方 我不喜欢产生行为或在视图中进行不必要的事件处理.所有代码都在一个地方,不需要额外的事件,ViewModel可以选择对每个值的变化做出反应,或者在用户操作结束时做出反应(这增加了大量的灵活性,特别是在使用绑定时).


Bag*_*rer 5

我的实现基于@Alan 和@SandRock 的回答:

public class SliderValueChangeByDragBehavior : Behavior<Slider>
    {
        private bool hasDragStarted;

        /// <summary>
        /// On behavior attached.
        /// </summary>
        protected override void OnAttached()
        {
            AssociatedObject.AddHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
            AssociatedObject.AddHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
            AssociatedObject.ValueChanged += Slider_ValueChanged;

            base.OnAttached();
        }

        /// <summary>
        /// On behavior detaching.
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            AssociatedObject.RemoveHandler(Thumb.DragStartedEvent, (DragStartedEventHandler)Slider_DragStarted);
            AssociatedObject.RemoveHandler(Thumb.DragCompletedEvent, (DragCompletedEventHandler)Slider_DragCompleted);
            AssociatedObject.ValueChanged -= Slider_ValueChanged;
        }

        private void updateValueBindingSource()
            => BindingOperations.GetBindingExpression(AssociatedObject, RangeBase.ValueProperty)?.UpdateSource();

        private void Slider_DragStarted(object sender, DragStartedEventArgs e)
            => hasDragStarted = true;

        private void Slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
        {
            hasDragStarted = false;
            updateValueBindingSource();
        }

        private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
        {
            if (!hasDragStarted)
                updateValueBindingSource();
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以这样应用它:

...
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:myWhateverNamespace="clr-namespace:My.Whatever.Namespace;assembly=My.Whatever.Assembly"
...

<Slider
                x:Name="srUserInterfaceScale"
                VerticalAlignment="Center"
                DockPanel.Dock="Bottom"
                IsMoveToPointEnabled="True"
                Maximum="{x:Static localLibraries:Library.MAX_USER_INTERFACE_SCALE}"
                Minimum="{x:Static localLibraries:Library.MIN_USER_INTERFACE_SCALE}"
                Value="{Binding Source={x:Static localProperties:Settings.Default}, Path=UserInterfaceScale, UpdateSourceTrigger=Explicit}">
                <i:Interaction.Behaviors>
                    <myWhateverNamespace:SliderValueChangeByDragBehavior />
                </i:Interaction.Behaviors>
            </Slider>
Run Code Online (Sandbox Code Playgroud)

我已将 UpdateSourceTrigger 设置为显式,正如行为所做的那样。并且您需要 nuget 包 Microsoft.Xaml.Behaviors(.Wpf/.Uwp.Managed)。