Slider does not drag in combination with IsMoveToPointEnabled behaviour
I have IsMoveToPointEnabled on for my Slider, so when I click an开发者_如何学Goywhere on the component the selector moves to my mouse. The problem is if I have this option on and click and hold the mouse down to drag the selector the selector doesn't move. Anyone know how to fix this?
The simplest way is to subclass Slider:
public class CustomSlider : Slider
{
public override void OnPreviewMouseMove(MouseEventArgs e)
{
if(e.LeftButton == MouseButtonState.Pressed)
OnPreviewMouseLeftButtonDown(e);
}
}
In which case your XAML would be:
<my:CustomSlider IsMoveToPointEnabled="True" />
For a more versatile solution that doesn't subclass Slider you can do it with an attached property:
public class SliderTools : DependencyObject
{
public static bool GetMoveToPointOnDrag(DependencyObject obj) { return (bool)obj.GetValue(MoveToPointOnDragProperty); }
public static void SetMoveToPointOnDrag(DependencyObject obj, bool value) { obj.SetValue(MoveToPointOnDragProperty, value); }
public static readonly DependencyProperty MoveToPointOnDragProperty = DependencyProperty.RegisterAttached("MoveToPointOnDrag", typeof(bool), typeof(SliderTools), new PropertyMetadata
{
PropertyChangedCallback = (obj, changeEvent) =>
{
var slider = (Slider)obj;
if((bool)changeEvent.NewValue)
slider.MouseMove += (obj2, mouseEvent) =>
{
if(mouseEvent.LeftButton == MouseButtonState.Pressed)
slider.RaiseEvent(new MouseButtonEventArgs(mouseEvent.MouseDevice, mouseEvent.Timestamp, MouseButton.Left)
{
RoutedEvent = UIElement.PreviewMouseLeftButtonDownEvent,
Source = mouseEvent.Source,
});
};
}
});
}
You would use this attached property on Slider along with the IsMoveToPointEnabled property:
<Slider IsMoveToPointEnabled="True" my:SliderTools.MoveToPointOnDrag="True" ... />
Both of these solutions work by converting PreviewMouseMove events into equivalent PreviewMouseLeftButtonDown events whenever the left button is down.
Note that the attached property does not remove the event handler when the property is set to false. I wrote it this way for simplicity since you almost never would need to remove such a handler. I recommend you stick with this simple solution, but if you want you can modify the PropertyChangedCallback to remove the handler when NewValue is false.
Inspired by Ray Burns Answer, the most simple way I found is this:
mySlider.PreviewMouseMove += (sender, args) =>
{
if (args.LeftButton == MouseButtonState.Pressed)
{
mySlider.RaiseEvent(new MouseButtonEventArgs(args.MouseDevice, args.Timestamp, MouseButton.Left)
{
RoutedEvent = UIElement.PreviewMouseLeftButtonDownEvent,
Source = args.Source
});
}
};
With mySlider being the name of my Slider.
There are two issues with this solution (and most others in this topic):
1. If you click and hold the mouse outside of the slider and then move it on the slider, the drag will start.
2. If you are using the slider's autotooltip, it will not work while dragging with this method.
So here is an improved version, that tackles both problems:
mySlider.MouseMove += (sender, args) =>
{
if (args.LeftButton == MouseButtonState.Pressed && this.clickedInSlider)
{
var thumb = (mySlider.Template.FindName("PART_Track", mySlider) as System.Windows.Controls.Primitives.Track).Thumb;
thumb.RaiseEvent(new MouseButtonEventArgs(args.MouseDevice, args.Timestamp, MouseButton.Left)
{
RoutedEvent = UIElement.MouseLeftButtonDownEvent,
Source = args.Source
});
}
};
mySlider.AddHandler(UIElement.PreviewMouseLeftButtonDownEvent, new RoutedEventHandler((sender, args) =>
{
clickedInSlider = true;
}), true);
mySlider.AddHandler(UIElement.PreviewMouseLeftButtonUpEvent, new RoutedEventHandler((sender, args) =>
{
clickedInSlider = false;
}), true);
clickedInSlider is a private helper variable defined somwhere in the class.
By using the clickedInSlider helper variable we avoid 1. The PreviewMouseButtonDown event is handled (because of MoveToPoint = true), so we have to use mySlider.AddHandler.
By raising the event on the Thumb instead of the Slider, we ensure that the autotooltip shows up.
Here is the improved version of @TimPohlmann's answer.
this one raises MouseButtonEventHandler
only once. although the thumb raises DragDeltaEventHandler
internally on every mouse move. but I found it unnecessary to fire MouseLeftButtonDownEvent
on thumb at every mouse move myself.
Also there is no need for helper variable.
This is implemented as behavior. AssociatedObject
is the slider that this behavior is attached to.
XAML:
<Slider ...>
<i:Interaction.Behaviors>
<slid:FreeSlideBehavior />
</i:Interaction.Behaviors>
</Slider>
Behavior:
public sealed class FreeSlideBehavior : Behavior<Slider>
{
private Thumb _thumb;
private Thumb Thumb
{
get
{
if (_thumb == null)
{
_thumb = ((Track)AssociatedObject.Template.FindName("PART_Track", AssociatedObject)).Thumb;
}
return _thumb;
}
}
protected override void OnAttached()
{
AssociatedObject.MouseMove += OnMouseMove;
}
protected override void OnDetaching()
{
AssociatedObject.MouseMove -= OnMouseMove;
}
private void OnMouseMove(object sender, MouseEventArgs args)
{
if (args.LeftButton == MouseButtonState.Released) return;
if(Thumb.IsDragging) return;
if (!Thumb.IsMouseOver) return;
Thumb.RaiseEvent(new MouseButtonEventArgs(args.MouseDevice, args.Timestamp, MouseButton.Left)
{
RoutedEvent = UIElement.MouseLeftButtonDownEvent
});
}
}
Binding to a mouse move event felt really wrong to me - I wanted a click on the slider to act like a click on the thumb so that the event only fires once and triggers normal dragging behavior. After doing some digging, I've come up with an alternative solution that works.
The Slider's OnPreviewMouseLeftButtonDown
method is what handles changing the Slider's value when IsMoveToPointEnabled
is true
. However, the Thumb's OnMouseLeftButtonDown
method is what handles grabbing focus, capturing the mouse, and then kicking off the DragStarted
event. So the strategy I took is to have the PreviewMouseLeftButtonDown
event on the Slider manually trigger the MouseLeftButtonDown
event on the Thumb:
mySlider.AddHandler(
PreviewMouseLeftButtonDownEvent,
new MouseButtonEventHandler((sender, e) =>
{
Track track = mySlider.Template.FindName("PART_Track", mySlider) as Track;
// It's important to check `track.Thumb.IsMouseOver`, because if it's `true` then
// the Thumb will already have its `OnMouseLeftButtonDown` method called - there's
// no need for us to manually trigger it (and doing so would result in firing the
// event twice, which is bad).
if (!mySlider.IsMoveToPointEnabled || track == null || track.Thumb == null || track.Thumb.IsMouseOver)
{
return;
}
// When `IsMoveToPointEnabled` is true, the Slider's `OnPreviewMouseLeftButtonDown`
// method updates the slider's value to where the user clicked. However, the Thumb
// hasn't had its position updated yet to reflect the change. As a result, we must
// call `UpdateLayout` on the Thumb to make sure its position is correct before we
// trigger a `MouseLeftButtonDownEvent` on it.
track.Thumb.UpdateLayout();
track.Thumb.RaiseEvent(new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left)
{
RoutedEvent = MouseLeftButtonDownEvent,
Source = track.Thumb
});
}),
true);
We have to use the AddHandler
overload that takes the third handledEventsToo
parameter on the Slider to hook up our handler, as the Slider's OnPreviewMouseLeftButtonDown
method sets the event's Handled
property to true
when IsMoveToPointEnabled
is true
. Alternatively, we could subclass Slider and override it's OnPreviewMouseLeftButton
method.
How about
private void slider_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Point point = e.GetPosition(slider);
slider.Value = point.X;
}
You need custom Slider and custom thumb. When you click in slider (not hit in thumb) -
var hitTestResult = VisualTreeHelper.HitTest(this, point); if (hitTestResult == null) return; var parent = hitTestResult.VisualHit.ParentOfType<Thumb>(); if (parent != null) return; _customThumb.OnMouseLeftButtonDown(e);
you can call in custom slider method, which call inside OnMouseLeftButtonDown(MouseButtonEventArgs e)
- You can you standart Thumb and use Reflaction for calling
OnMouseLeftButtonDown(MouseButtonEventArgs e)
精彩评论