Нажав среднюю кнопку мыши (ака: колесико мыши), а затем слегка перемещая мышь, пользователи могут прокручиваться в IE и большинстве приложений Windows. Поведение, по-видимому, отсутствует в элементах управления WPF по умолчанию? Есть ли настройка, обходной путь или что-то очевидное, что мне не хватает?
Как я могу сделать прокрутку WPF ScrollViewer средним щелчком?
Ответ 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();
}
Пример значков:
И последнее замечание: есть проблемы со способом 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"
.