62 votes

Comment puis-je avoir un défilement automatique ListBox lorsqu'un nouvel élément est ajouté?

J'ai un WPF zone de liste qui est mis à faire défiler horizontalement. Le ItemsSource est lié à une ObservableCollection dans ma classe ViewModel. Chaque fois qu'un nouvel élément est ajouté, je veux la liste pour faire défiler vers la droite de sorte que le nouvel élément est visible.

La zone de liste est définie dans un DataTemplate, donc je suis incapable d'accéder à la zone de liste par nom dans mon fichier code-behind.

Comment puis-je obtenir un contrôle ListBox à toujours faites défiler jusqu'à afficher un dernier élément ajouté?

Je voudrais un moyen de savoir si la zone de liste a un nouvel élément, ajouté à cela, mais je ne vois pas un événement qui ne de cette.

71voto

Aviad P. Points 9351

Vous pouvez étendre le comportement du ListBox en utilisant des propriétés attachées. Dans votre cas, je définirais une propriété attachée appelée ScrollOnNewItem qui, lorsqu'elle est définie sur true s'accroche aux événements INotifyCollectionChanged de la boîte à liste source et lors de la détection d'une nouvelle item, fait défiler la liste vers la liste.

Exemple:

 public class ListBoxBehavior
{
    static Dictionary<ListBox, Capture> Associations = 
        new Dictionary<ListBox, Capture>();

    public static bool GetScrollOnNewItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(ScrollOnNewItemProperty);
    }

    public static void SetScrollOnNewItem(DependencyObject obj, bool value)
    {
        obj.SetValue(ScrollOnNewItemProperty, value);
    }

    public static readonly DependencyProperty ScrollOnNewItemProperty =
        DependencyProperty.RegisterAttached(
            "ScrollOnNewItem", 
            typeof(bool), 
            typeof(ListBoxBehavior), 
            new UIPropertyMetadata(false, OnScrollOnNewItemChanged));

    public static void OnScrollOnNewItemChanged(
        DependencyObject d, 
        DependencyPropertyChangedEventArgs e)
    {
        var listBox = d as ListBox;
        if (listBox == null) return;
        bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;
        if (newValue == oldValue) return;
        if (newValue)
        {
            listBox.Loaded += new RoutedEventHandler(ListBox_Loaded);
            listBox.Unloaded += new RoutedEventHandler(ListBox_Unloaded);
        }
        else
        {
            listBox.Loaded -= ListBox_Loaded;
            listBox.Unloaded -= ListBox_Unloaded;
            if (Associations.ContainsKey(listBox))
                Associations[listBox].Dispose();
        }
    }

    static void ListBox_Unloaded(object sender, RoutedEventArgs e)
    {
        var listBox = (ListBox)sender;
        if (Associations.ContainsKey(listBox))
            Associations[listBox].Dispose();
        listBox.Unloaded -= ListBox_Unloaded;
    }

    static void ListBox_Loaded(object sender, RoutedEventArgs e)
    {
        var listBox = (ListBox)sender;
        var incc = listBox.Items as INotifyCollectionChanged;
        if (incc == null) return;
        listBox.Loaded -= ListBox_Loaded;
        Associations[listBox] = new Capture(listBox);
    }

    class Capture : IDisposable
    {
        public ListBox listBox { get; set; }
        public INotifyCollectionChanged incc { get; set; }

        public Capture(ListBox listBox)
        {
            this.listBox = listBox;
            incc = listBox.ItemsSource as INotifyCollectionChanged;
            if(incc != null)
            {
                incc.CollectionChanged += 
                    new NotifyCollectionChangedEventHandler(incc_CollectionChanged);
            }
        }

        void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                listBox.ScrollIntoView(e.NewItems[0]);
                listBox.SelectedItem = e.NewItems[0];
            }
        }

        public void Dispose()
        {
            if(incc != null)
                incc.CollectionChanged -= incc_CollectionChanged;
        }
    }
}
 

Usage:

 <ListBox ItemsSource="{Binding SourceCollection}" 
         lb:ListBoxBehavior.ScrollOnNewItem="true"/>
 

24voto

denis morozov Points 2779
<ItemsControl ItemsSource="{Binding SourceCollection}">
    <i:Interaction.Behaviors>
        <Behaviors:ScrollOnNewItem/>
    </i:Interaction.Behaviors>              
</ItemsControl>

public class ScrollOnNewItem : Behavior<ItemsControl>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += OnUnLoaded;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= OnLoaded;
        AssociatedObject.Unloaded -= OnUnLoaded;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged += OnCollectionChanged;
    }

    private void OnUnLoaded(object sender, RoutedEventArgs e)
    {
        var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
        if (incc == null) return;

        incc.CollectionChanged -= OnCollectionChanged;
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if(e.Action == NotifyCollectionChangedAction.Add)
        {
            int count = AssociatedObject.Items.Count;
            if (count == 0) 
                return; 

            var item = AssociatedObject.Items[count - 1];

            var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
            if (frameworkElement == null) return;

            frameworkElement.BringIntoView();
        }
    }

22voto

shawnpfiore Points 21

J'ai trouvé un moyen très astucieux de le faire, il suffit de mettre à jour le listView scrollViewer et de régler la position au bas. Appelez cette fonction dans l'un des événements ListBox, comme SelectionChanged par exemple.

  private void UpdateScrollBar(ListBox listBox)
    {
        if (listBox != null)
        {
            var border = (Border)VisualTreeHelper.GetChild(listBox, 0);
            var scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
            scrollViewer.ScrollToBottom();
        }

    }
 

10voto

Shuo Points 1655

J'utilise cette solution: http://michlg.wordpress.com/2010/01/16/listbox-automatically-scroll-currentitem-into-view/ .

Cela fonctionne même si vous liez la source ItemsSource de listbox à un ObservableCollection manipulé dans un thread non-UI.

2voto

JoKe Points 1

solution pour Datagrid (identique pour ListBox, remplacez seulement DataGrid par la classe ListBox)

     private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            int count = AssociatedObject.Items.Count;
            if (count == 0)
                return;

            var item = AssociatedObject.Items[count - 1];

            if (AssociatedObject is DataGrid)
            {
                DataGrid grid = (AssociatedObject as DataGrid);
                grid.Dispatcher.BeginInvoke((Action)(() =>
                {
                    grid.UpdateLayout();
                    grid.ScrollIntoView(item, null);
                }));
            }

        }
    }
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X