2011-03-24 13 views
11

Al hacer clic en el botón central del mouse (también conocido como: rueda del mouse) y luego mover el mouse hacia abajo, permite a los usuarios desplazarse en IE y en la mayoría de las aplicaciones de Windows. Este comportamiento parece faltar en los controles de WPF por defecto? ¿Hay alguna configuración, una solución alternativa o algo obvio que me falta?¿Cómo puedo hacer que WPF ScrollViewer haga clic con el botón central del mouse?

+1

Es posible controlar el evento del botón central del mouse: http://stackoverflow.com/questions/517556/how-to-handle-the-mouse-wheel-click-event-in-wpf. Trataré de manejar y procesar este evento correctamente. – vorrtex

Respuesta

11

He encontrado cómo lograr esto usando 3 eventos de mouse (MouseDown, MouseUp, MouseMove). Sus manejadores están unidos al elemento ScrollViewer en el xaml a continuación:

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

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

Sería mejor escribir un comportamiento en lugar de los acontecimientos en código subyacente, pero no todos tienen la biblioteca necesaria, y también no hacer saber cómo conectarlo con el Canvas.

Los controladores de eventos:

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

Si para eliminar el método llama AddScrollSign y RemoveScrollSign este ejemplo va a funcionar. Pero he ampliado con 2 métodos que establecen icono de desplazamiento:

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

ejemplo de iconos: enter image description hereenter image description here

Y una última observación: hay algunos problemas con la forma Press -> Immediately Release -> Move. Se supone que debe cancelar el desplazamiento si un usuario hace clic con el botón izquierdo del mouse, o cualquier tecla del teclado, o la aplicación pierde el foco. Hay muchos eventos y no tengo tiempo para manejarlos a todos.

Pero la forma estándar Press -> Move -> Release funciona sin problemas.

+0

+1 para la solución, tengo algunas sugerencias, por favor vea mi respuesta por separado –

2

vorrtex ha publicado una solución agradable, Por favor, vuélvela él!

Aunque tengo algunas sugerencias para su solución, que son demasiado largas para incluirlas todas en los comentarios, ¡es por eso que publico una respuesta separada y se la dirijo!

Menciona problemas con Presione-> Soltar-> Mover. Debería utilizar MouseCapturing para obtener MouseEvents incluso cuando el mouse ya no esté sobre el ScrollViewer. No lo he probado, pero supongo que su solución también falla en Press->Move->Move outside of ScrollViewer->Release, Mousecapturing se encargará de eso también.

También menciona usar un Comportamiento. Prefiero sugerir un attached behavior que no necesita dependencias adicionales.

Definitivamente no debe utilizar un lienzo adicional, pero haga esto en un Adorner.

ScrollViewer alberga un ScrollContentPresenter que define AdornerLayer. Deberías insertar el Adorner allí. Esto elimina la necesidad de cualquier dependencia adicional y también mantiene el comportamiento adjunto tan simple como IsMiddleScrollable="true".

+0

MouseCapture no es difícil de implementar, lo haré después de algunas horas.Pero otros comentarios no son tan claros. Si utilizo propiedad o comportamiento adjunto, no podré agregar la imagen de desplazamiento al 'Lienzo'. Por otro lado, el control 'ScrollContentPresenter 'se encuentra dentro de la plantilla de' ScrollViewer' y creo que debo crear un control heredado para acceder a las partes internas. Trataré de hacer algo con esto, pero no estoy seguro de si el nuevo control es mejor que un conjunto de controladores de eventos. – vorrtex

+0

Después de capturar el mouse, el evento MouseMove se llama solo una vez. Debo crear un temporizador que activará el método con la funcionalidad de movimiento. Todo se vuelve más y más complejo. – vorrtex

+0

@vorrtex Tendré tiempo mañana por la noche. Como también estoy interesado en una buena solución compacta, ¡me sumergiré en ella! –

Cuestiones relacionadas