Ползунок не перетаскивается в сочетании с поведением IsMoveToPointEnabled

У меня есть IsMoveToPointEnabled для моего слайдера, поэтому, когда я нажимаю в любом месте компонента, селектор перемещается к моей мыши. Проблема в том, что если у меня есть эта опция и нажмите и удерживайте мышь, чтобы перетащить селектор, селектор не перемещается. Кто-нибудь знает, как это исправить?

Ответ 1

Самый простой способ - подкласс Slider:

public class CustomSlider : Slider
{
  public override void OnPreviewMouseMove(MouseEventArgs e)
  {
    if(e.LeftButton == MouseButtonState.Pressed)
      OnPreviewMouseLeftButtonDown(e);
  }
}

В этом случае ваш XAML будет:

<my:CustomSlider IsMoveToPointEnabled="True" />

Для более универсального решения, которое не является подклассом Slider, вы можете сделать это с прикрепленным свойством:

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

Вы использовали бы это вложенное свойство в Slider вместе со свойством IsMoveToPointEnabled:

<Slider IsMoveToPointEnabled="True" my:SliderTools.MoveToPointOnDrag="True" ... />

Оба этих решения работают путем преобразования событий PreviewMouseMove в эквивалентные события PreviewMouseLeftButtonDown всякий раз, когда левая кнопка не работает.

Обратите внимание, что вложенное свойство не удаляет обработчик события, если для свойства установлено значение false. Я написал это для простоты, так как вам почти никогда не нужно было удалять такой обработчик. Я рекомендую вам придерживаться этого простого решения, но если вы хотите, вы можете изменить PropertyChangedCallback, чтобы удалить обработчик, когда NewValue является ложным.

Ответ 2

Вдохновленный Ray Burns Ответ, самый простой способ, который я нашел, заключается в следующем:

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

Когда mySlider является именем моего слайдера.

Есть два вопроса с этим решением (и большинство других в этом разделе):
1. Если вы нажмете и удерживаете мышь за пределами ползунка, а затем переместите ее на ползунок, начнется перетаскивание.
2. Если вы используете автотекст ползунка, он не будет работать при перетаскивании с помощью этого метода.

Итак, вот улучшенная версия, которая решает обе проблемы:

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 - это частная вспомогательная переменная, определенная где-то в классе.

С помощью хелперной переменной clickedInSlider мы избегаем 1. Событие PreviewMouseButtonDown обрабатывается (из-за MoveToPoint = true), поэтому мы должны использовать mySlider.AddHandler.
Подняв событие на Thumb вместо Slider, мы гарантируем, что появится autotooltip.

Ответ 3

Вот улучшенная версия ответа @TimPohlmann.

этот воскрешает MouseButtonEventHandler только один раз. хотя большой палец поднимает DragDeltaEventHandler внутри каждого движения мыши. но я не счел нужным стрелять MouseLeftButtonDownEvent на большой палец на каждом шаге мыши.

Также нет необходимости в вспомогательной переменной.

Это реализовано как поведение. AssociatedObject - это слайдер, к которому это поведение привязано.

XAML:

<Slider ...>
    <i:Interaction.Behaviors>
        <slid:FreeSlideBehavior />
    </i:Interaction.Behaviors>
</Slider>

Поведение:

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

Ответ 4

Как насчет

    private void slider_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        Point point = e.GetPosition(slider);
        slider.Value = point.X;
    }

Ответ 5

  • Вам нужен пользовательский слайдер и пользовательский палец. Когда вы нажимаете ползунок (не попадаете большим пальцем) -

    var hitTestResult = VisualTreeHelper.HitTest(this, point);
    if (hitTestResult == null) return;
    var parent = hitTestResult.VisualHit.ParentOfType<Thumb>();
    if (parent != null) return;
    _customThumb.OnMouseLeftButtonDown(e);
    

вы можете вызвать пользовательский метод слайдера, который вызывает внутри OnMouseLeftButtonDown(MouseButtonEventArgs e)

  1. Вы можете использовать стандартный Thumb и использовать Reflaction для вызова OnMouseLeftButtonDown(MouseButtonEventArgs e)