Как я могу сделать прокрутку WPF ScrollViewer средним щелчком?

Нажав среднюю кнопку мыши (ака: колесико мыши), а затем слегка перемещая мышь, пользователи могут прокручиваться в IE и большинстве приложений Windows. Поведение, по-видимому, отсутствует в элементах управления WPF по умолчанию? Есть ли настройка, обходной путь или что-то очевидное, что мне не хватает?

Ответ 1

Я нашел, как достичь этого, используя 3 события мыши (MouseDown, MouseUp, MouseMove). Их обработчики привязаны к элементу ScrollViewer в xaml ниже:

<Grid>
    <ScrollViewer MouseDown="ScrollViewer_MouseDown" MouseUp="ScrollViewer_MouseUp" MouseMove="ScrollViewer_MouseMove">
            <StackPanel x:Name="dynamicLongStackPanel">

            </StackPanel>
    </ScrollViewer>
    <Canvas x:Name="topLayer" IsHitTestVisible="False" />
</Grid>

Лучше написать поведение вместо событий в кодировке, но не у всех есть необходимая библиотека, а также я не знаю, как подключить ее к Canvas.

Обработчики событий:

    private bool isMoving = false;                  //False - ignore mouse movements and don't scroll
    private bool isDeferredMovingStarted = false;   //True - Mouse down -> Mouse up without moving -> Move; False - Mouse down -> Move
    private Point? startPosition = null;
    private double slowdown = 200;                  //The number 200 is found from experiments, it should be corrected



    private void ScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (this.isMoving == true) //Moving with a released wheel and pressing a button
                this.CancelScrolling();
        else if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Pressed)
        {
            if (this.isMoving == false) //Pressing a wheel the first time
            {
                this.isMoving = true;
                this.startPosition = e.GetPosition(sender as IInputElement);
                this.isDeferredMovingStarted = true; //the default value is true until the opposite value is set

                this.AddScrollSign(e.GetPosition(this.topLayer).X, e.GetPosition(this.topLayer).Y);
            }
        }
    }

    private void ScrollViewer_MouseUp(object sender, MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Middle && e.ButtonState == MouseButtonState.Released && this.isDeferredMovingStarted != true)
            this.CancelScrolling();
    }

    private void CancelScrolling()
    {
        this.isMoving = false;
        this.startPosition = null;
        this.isDeferredMovingStarted = false;
        this.RemoveScrollSign();
    }

    private void ScrollViewer_MouseMove(object sender, MouseEventArgs e)
    {
        var sv = sender as ScrollViewer;

        if (this.isMoving && sv != null)
        {
            this.isDeferredMovingStarted = false; //standard scrolling (Mouse down -> Move)

            var currentPosition = e.GetPosition(sv);
            var offset = currentPosition - startPosition.Value;
            offset.Y /= slowdown;
            offset.X /= slowdown;

            //if(Math.Abs(offset.Y) > 25.0/slowdown)  //Some kind of a dead space, uncomment if it is neccessary
            sv.ScrollToVerticalOffset(sv.VerticalOffset + offset.Y);
            sv.ScrollToHorizontalOffset(sv.HorizontalOffset + offset.X);
        }
    }

Если для удаления метода вызовы AddScrollSign и RemoveScrollSign, этот пример будет работать. Но я расширил его двумя способами, которые устанавливают значок прокрутки:

    private void AddScrollSign(double x, double y)
    {
        int size = 50;
        var img = new BitmapImage(new Uri(@"d:\middle_button_scroll.png"));
        var adorner = new Image() { Source = img, Width = size, Height = size };
        //var adorner = new Ellipse { Stroke = Brushes.Red, StrokeThickness = 2.0, Width = 20, Height = 20 };

        this.topLayer.Children.Add(adorner);
        Canvas.SetLeft(adorner, x - size / 2);
        Canvas.SetTop(adorner, y - size / 2);
    }

    private void RemoveScrollSign()
    {
        this.topLayer.Children.Clear();
    }

Пример значков: enter image description hereenter image description here

И последнее замечание: есть проблемы со способом Press -> Immediately Release -> Move. Предполагается отменить прокрутку, если пользователь нажимает левую кнопку мыши или любую клавишу клавиатуры, или приложение теряет фокус. Есть много событий, и у меня нет времени на их обработку.

Но стандартный способ Press -> Move -> Release работает без проблем.

Ответ 2

vorrtex разместил хорошее решение, , пожалуйста, поддержите его!

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

Вы упомянули проблемы с Press- > Release- > Move. Вы должны использовать MouseCapturing для получения MouseEvents, даже если мышь больше не находится над ScrollViewer. Я не тестировал его, но, думаю, ваше решение также терпит неудачу в Press->Move->Move outside of ScrollViewer->Release, Mousecapturing тоже позаботится об этом.

Также упоминается использование Поведения. Я предпочел бы предложить прикрепленное поведение, которое не требует дополнительных зависимостей.

Вы должны определенно не использовать дополнительный холст, но делать это у Adorner.

ScrollViewer сам содержит ScrollContentPresenter, который определяет AdornerLayer. Вы должны вставить там Adorner. Это устраняет необходимость в любой дополнительной зависимости, а также сохраняет приложенное поведение так же просто, как IsMiddleScrollable="true".