2009-08-22 11 views
20

Ich habe eine WPF-App, die eine ListBox hat. Der Drag-Mechanismus ist bereits implementiert, aber wenn die Liste zu lang ist und ich ein Element an eine Position verschieben möchte, die nicht sichtbar ist, kann ich das nicht.WPF Listbox automatisches Scrollen beim Ziehen

Zum Beispiel zeigt der Bildschirm 10 Elemente an. Und ich habe 20 Gegenstände. Wenn ich den letzten Gegenstand zur ersten Position ziehen möchte, muss ich nach oben ziehen und fallen lassen. Scrollen Sie nach oben und ziehen Sie erneut.

Wie kann ich die ListBox Auto Scroll machen?

Antwort

25

Verstanden. Benutzte das Ereignis DragOver des ListBox, verwendete die Funktion here, um die scrollviewer der Listbox zu bekommen, und danach war es nur ein bisschen Jonglieren mit der Position.

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

ich Ihnen Methode versucht und es funktioniert. Wenn Objekte jedoch nach dem Löschen in derselben Liste gezogen werden, kehrt sie zum ursprünglichen Objekt zurück, an dem ich das gelöschte Objekt sehen möchte. Hast du das und hast du es korrigiert? –

+0

@DavidBrunelle Ich kann mich nicht erinnern, sorry. –

+0

+1 große Antwort, obwohl das eine Tiefensuche zuerst ist, nicht eine Breite zuerst wie angegeben. – Cameron

13

Auf dieser Grundlage habe ich eine Attached Behavior geschaffen, die leicht wie folgt verwendet werden kann -

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

Hier ist der Code für angebracht Verhalten ist -

/// <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

Sehr nette Lösung. Vergessen Sie nicht, dass Sie "ScrollViewer.CanContentScroll =" False "" auf Ihrer ListBox/ListView setzen können, wenn Sie einen sanften Bildlauf wünschen. – Pak