2009-08-22 16 views
20

Ho un'app WPF che ha un ListBox. Il meccanismo di trascinamento è già implementato, ma quando l'elenco è troppo lungo e voglio spostare un oggetto in una posizione non visibile non posso.WPF Listbox auto scroll durante il trascinamento

Ad esempio, lo schermo mostra 10 voci. E ho 20 oggetti. Se voglio trascinare l'ultimo elemento nella prima posizione, devo trascinare verso l'alto e rilasciare. Scorri verso l'alto e trascina di nuovo.

Come posso eseguire lo scorrimento automatico ListBox?

risposta

25

Capito. Utilizzato l'evento ListBox, l'evento ListBox, ha utilizzato la funzione trovata here per ottenere il numero scrollviewer della casella di riepilogo e dopo è solo un po 'di giocoleria con la Posizione.

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

Ho provato a metodo e lavori. Tuttavia, trascinando gli oggetti attorno allo stesso elenco, dopo il rilascio, ritorna all'oggetto originale dove vorrei vedere l'elemento rilasciato. Hai questo e l'hai corretto? –

+0

@DavidBrunelle Non ricordo, mi dispiace. –

+0

+1 ottima risposta, anche se si tratta di una ricerca in profondità, non di una prima di larghezza come indicato. – Cameron

13

Sulla base di questo ho creato un Attached Behavior che può essere facilmente utilizzato in questo modo -

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

Ecco il codice per il comportamento allegato -

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

Soluzione molto bella. Non dimenticare che puoi inserire "ScrollViewer.CanContentScroll =" False "" sul tuo ListBox/ListView se vuoi una scroll scorrevole. – Pak