WPF:带有用户拖动后触发的事件的滑块
我目前正在制作一个WPF的MP3播放器,我想制作一个滑块,让用户通过向左或向右滑动滑块来寻找MP3中的特定位置。
我曾尝试使用ValueChanged
事件,但每次它的值被改变时触发,所以如果你拖动它,事件将多次触发, 我希望事件只在用户完成拖动滑块时触发,然后获取新的价值 。
我怎样才能做到这一点?
[更新]
我在MSDN上发现了这个post ,基本上讨论了同样的事情,他们提出了两个“解决scheme”。 要么在Slider子类中调用,要么调用ValueChanged
事件中的DispatcherTimer
,该事件在时间范围之后调用该操作。
你可以拿出上面提到的两个更好的东西吗?
您可以使用拇指的“DragCompleted”事件。 不幸的是,只有拖动时才会触发,因此您需要分别处理其他点击和按键。 如果您只希望它是可拖动的,则可以通过将LargeChange设置为0并将Focusable设置为false来禁用这些移动滑块的方法。
例:
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
除了使用Thumb.DragCompleted
事件,您还可以同时使用ValueChanged
和Thumb.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(); } }