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

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

我曾尝试使用ValueChanged事件,但每次它的值被改变时触发,所以如果你拖动它,事件将多次触发, 我希望事件只在用户完成拖动滑块时触发,然后获取新的价值

我怎样才能做到这一点?


[更新]

我在MSDN上发现了这个post ,基本上讨论了同样的事情,他们提出了两个“解决scheme”。 要么在Slider子类中调用,要么调用ValueChanged事件中的DispatcherTimer ,该事件在时间范围之后调用该操作。

你可以拿出上面提到的两个更好的东西吗?

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

例:

 <Slider Thumb.DragCompleted="MySlider_DragCompleted" /> 

除了使用Thumb.DragCompleted事件,您还可以同时使用ValueChangedThumb.DragStarted ,这样,当用户通过按方向键或单击滑块来修改值时,不会失去function。

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不会隧道(它可以被处理),所以你必须使用PreviewMouseUp。

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

视图:

 <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毫秒)操作。 为滑块完成的每个更改(通过鼠标或键盘)创build新任务。 在启动任务之前, 信号 (通过使用Monitor.PulseAll ,甚至Monitor.Pulse就足够了)来运行已经完成的任务(如果有的话)。 只有当Monitor.Wait在超时时间内没有得到信号时,才会发生部分事情

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

这是一个处理这个问题的行为加上与键盘相同的事情。 https://gist.github.com/4326429

它公开了一个Command和Value属性。 该值作为命令的parameter passing。 你可以绑定到value属性(并在viewmodel中使用它)。 您可以为代码隐藏方法添加事件处理程序。

 <Slider> <i:Interaction.Behaviors> <b:SliderValueChangedBehavior Command="{Binding ValueChangedCommand}" Value="{Binding MyValue}" /> </i:Interaction.Behaviors> </Slider> 
 <Slider x:Name="PositionSlider" Minimum="0" Maximum="100"></Slider> PositionSlider.LostMouseCapture += new MouseEventHandler(Position_LostMouseCapture); PositionSlider.AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(Position_DragCompleted)); 

我的解决scheme基本上是Santo的解决scheme,有更多的标志。 对我来说,滑块正在从读取stream或用户操作(从鼠标拖动或使用箭头键等)更新,

首先,我编写了代码来读取stream更新滑块值:

  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; } 

当用户用鼠标拖动来更新滑块的值时,由于正在读取stream并调用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() 。 这是由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 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成员将为true。 一旦完成,它将被重置为假。

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

您想要的滑块手推车的子版本:

 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(); } }