2009-08-22 7 views
20

Tengo una aplicación WPF que tiene un ListBox. El mecanismo de arrastre ya está implementado, pero cuando la lista es demasiado larga y quiero mover un elemento a una posición no visible, no puedo.Desplazamiento automático de la lista de WPF arrastrando

Por ejemplo, la pantalla muestra 10 elementos. Y tengo 20 artículos. Si quiero arrastrar el último elemento a la primera posición, debo arrastrarlo a la parte superior y soltarlo. Desplácese hacia arriba y arrastre nuevamente.

¿Cómo puedo hacer el desplazamiento automático ListBox?

Respuesta

25

Lo tengo. Usó el evento DragOver del ListBox, usó la función que se encontró here para obtener el scrollviewer del cuadro de lista y después de eso es solo un poco de malabares con la posición.

private void ItemsList_DragOver(object sender, System.Windows.DragEventArgs e) 
{ 
    ListBox li = sender as ListBox; 
    ScrollViewer sv = FindVisualChild<ScrollViewer>(ItemsList); 

    double tolerance = 10; 
    double verticalPos = e.GetPosition(li).Y; 
    double offset = 3; 

    if (verticalPos < tolerance) // Top of visible list? 
    { 
     sv.ScrollToVerticalOffset(sv.VerticalOffset - offset); //Scroll up. 
    } 
    else if (verticalPos > li.ActualHeight - tolerance) //Bottom of visible list? 
    { 
     sv.ScrollToVerticalOffset(sv.VerticalOffset + offset); //Scroll down.  
    } 
} 

public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject 
{ 
    // Search immediate children first (breadth-first) 
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) 
    { 
     DependencyObject child = VisualTreeHelper.GetChild(obj, i); 

     if (child != null && child is childItem) 
      return (childItem)child; 

     else 
     { 
      childItem childOfChild = FindVisualChild<childItem>(child); 

      if (childOfChild != null) 
       return childOfChild; 
     } 
    } 

    return null; 
} 
+1

He intentado que el método y trabajos. Sin embargo, al arrastrar objetos alrededor de la misma lista, después de droping, regresa al objeto original donde me gustaría ver el elemento caído. ¿Tuviste esto y lo corrigiste? –

+0

@DavidBrunelle No lo recuerdo, lo siento. –

+0

+1 gran respuesta, aunque es una búsqueda en profundidad, no la primera, como se indica. – Cameron

13

Basado en esto, he creado una Attached Behavior que puede ser fácilmente utilizado como esto -

<ListView 
    xmlns:WpfExtensions="clr-namespace:WpfExtensions" 
    WpfExtensions:DragDropExtension.ScrollOnDragDrop="True" 

Aquí es el código de comportamiento adjunto -

/// <summary> 
/// Provides extended support for drag drop operation 
/// </summary> 
public static class DragDropExtension 
{ 
    public static read-only DependencyProperty ScrollOnDragDropProperty = 
     DependencyProperty.RegisterAttached("ScrollOnDragDrop", 
      typeof(bool), 
      typeof(DragDropExtension), 
      new PropertyMetadata(false, HandleScrollOnDragDropChanged)); 

    public static bool GetScrollOnDragDrop(DependencyObject element) 
    { 
     if (element == null) 
     { 
      throw new ArgumentNullException("element"); 
     } 

     return (bool)element.GetValue(ScrollOnDragDropProperty); 
    } 

    public static void SetScrollOnDragDrop(DependencyObject element, bool value) 
    { 
     if (element == null) 
     { 
      throw new ArgumentNullException("element"); 
     } 

     element.SetValue(ScrollOnDragDropProperty, value); 
    } 

    private static void HandleScrollOnDragDropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     FrameworkElement container = d as FrameworkElement; 

     if (d == null) 
     { 
      Debug.Fail("Invalid type!"); 
      return; 
     } 

     Unsubscribe(container); 

     if (true.Equals(e.NewValue)) 
     { 
      Subscribe(container); 
     } 
    } 

    private static void Subscribe(FrameworkElement container) 
    { 
     container.PreviewDragOver += OnContainerPreviewDragOver; 
    } 

    private static void OnContainerPreviewDragOver(object sender, DragEventArgs e) 
    { 
     FrameworkElement container = sender as FrameworkElement; 

     if (container == null) 
     { 
      return; 
     } 

     ScrollViewer scrollViewer = GetFirstVisualChild<ScrollViewer>(container); 

     if (scrollViewer == null) 
     { 
      return; 
     } 

     double tolerance = 60; 
     double verticalPos = e.GetPosition(container).Y; 
     double offset = 20; 

     if (verticalPos < tolerance) // Top of visible list? 
     { 
      scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset); //Scroll up. 
     } 
     else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list? 
     { 
      scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset); //Scroll down.  
     } 
    } 

    private static void Unsubscribe(FrameworkElement container) 
    { 
     container.PreviewDragOver -= OnContainerPreviewDragOver; 
    } 

    public static T GetFirstVisualChild<T>(DependencyObject depObj) where T : DependencyObject 
    { 
     if (depObj != null) 
     { 
      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) 
      { 
       DependencyObject child = VisualTreeHelper.GetChild(depObj, i); 
       if (child != null && child is T) 
       { 
        return (T)child; 
       } 

       T childItem = GetFirstVisualChild<T>(child); 
       if (childItem != null) 
       { 
        return childItem; 
       } 
      } 
     } 

     return null; 
    } 
} 
+2

Muy buena solución. No olvide que puede poner "ScrollViewer.CanContentScroll =" False "" en su ListBox/ListView si desea un desplazamiento suave. – Pak

Cuestiones relacionadas