web-dev-qa-db-fra.com

Faire ListView.ScrollIntoView Faire défiler l'élément au centre de ListView (C #)

ListView.ScrollIntoView(object) trouve actuellement un objet dans le ListView et y fait défiler. Si vous êtes positionné sous l'objet vers lequel vous faites défiler, il fait défiler l'objet jusqu'à la ligne supérieure. Si vous êtes positionné au-dessus, il le fait défiler dans la vue de la rangée du bas.

J'aimerais que l'élément défile vers le bas au centre de ma vue de liste s'il n'est actuellement pas visible. Y a-t-il un moyen facile d'accomplir cela?

52
Kirk Ouimet

Il est très facile de le faire dans WPF avec une méthode d'extension que j'ai écrite. Tout ce que vous avez à faire pour faire défiler un élément vers le centre de la vue est d'appeler une seule méthode.

Supposons que vous ayez ce XAML:

<ListView x:Name="view" ItemsSource="{Binding Data}" /> 
<ComboBox x:Name="box"  ItemsSource="{Binding Data}"
                        SelectionChanged="ScrollIntoView" /> 

Votre méthode ScrollIntoView sera simplement:

private void ScrollIntoView(object sender, SelectionChangedEventArgs e)
{
  view.ScrollToCenterOfView(box.SelectedItem);
} 

De toute évidence, cela pourrait également être fait à l'aide d'un ViewModel plutôt que de référencer explicitement les contrôles.

Voici la mise en œuvre. C'est très général, il gère toutes les possibilités d'IScrollInfo. Il fonctionne avec ListBox ou tout autre élément ItemsControl, et fonctionne avec n'importe quel panneau, y compris StackPanel, VirtualizingStackPanel, WrapPanel, DockPanel, Canvas, Grid, etc.

Placez-le simplement dans un fichier .cs quelque part dans votre projet:

public static class ItemsControlExtensions
{
  public static void ScrollToCenterOfView(this ItemsControl itemsControl, object item)
  {
    // Scroll immediately if possible
    if(!itemsControl.TryScrollToCenterOfView(item))
    {
      // Otherwise wait until everything is loaded, then scroll
      if(itemsControl is ListBox) ((ListBox)itemsControl).ScrollIntoView(item);
      itemsControl.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
        {
          itemsControl.TryScrollToCenterOfView(item);
        }));
    }
  }

  private static bool TryScrollToCenterOfView(this ItemsControl itemsControl, object item)
  {
    // Find the container
    var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
    if(container==null) return false;

    // Find the ScrollContentPresenter
    ScrollContentPresenter presenter = null;
    for(Visual vis = container; vis!=null && vis!=itemsControl; vis = VisualTreeHelper.GetParent(vis) as Visual)
      if((presenter = vis as ScrollContentPresenter)!=null)
        break;
    if(presenter==null) return false;

    // Find the IScrollInfo
    var scrollInfo = 
        !presenter.CanContentScroll ? presenter :
        presenter.Content as IScrollInfo ??
        FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
        presenter;

    // Compute the center point of the container relative to the scrollInfo
    Size size = container.RenderSize;
    Point center = container.TransformToAncestor((Visual)scrollInfo).Transform(new Point(size.Width/2, size.Height/2));
    center.Y += scrollInfo.VerticalOffset;
    center.X += scrollInfo.HorizontalOffset;

    // Adjust for logical scrolling
    if(scrollInfo is StackPanel || scrollInfo is VirtualizingStackPanel)
    {
      double logicalCenter = itemsControl.ItemContainerGenerator.IndexFromContainer(container) + 0.5;
      Orientation orientation = scrollInfo is StackPanel ? ((StackPanel)scrollInfo).Orientation : ((VirtualizingStackPanel)scrollInfo).Orientation;
      if(orientation==Orientation.Horizontal)
        center.X = logicalCenter;
      else
        center.Y = logicalCenter;
    }

    // Scroll the center of the container to the center of the viewport
    if(scrollInfo.CanVerticallyScroll) scrollInfo.SetVerticalOffset(CenteringOffset(center.Y, scrollInfo.ViewportHeight, scrollInfo.ExtentHeight));
    if(scrollInfo.CanHorizontallyScroll) scrollInfo.SetHorizontalOffset(CenteringOffset(center.X, scrollInfo.ViewportWidth, scrollInfo.ExtentWidth));
    return true;
  }

  private static double CenteringOffset(double center, double viewport, double extent)
  {
    return Math.Min(extent - viewport, Math.Max(0, center - viewport/2));
  }
  private static DependencyObject FirstVisualChild(Visual visual)
  {
    if(visual==null) return null;
    if(VisualTreeHelper.GetChildrenCount(visual)==0) return null;
    return VisualTreeHelper.GetChild(visual, 0);
  }
}
78
Ray Burns

L'excellente réponse de Ray Burns ci-dessus est spécifique à WPF.

Voici une version modifiée qui fonctionne dans Silverlight:

 public static class ItemsControlExtensions
    {
        public static void ScrollToCenterOfView(this ItemsControl itemsControl, object item)
        {
            // Scroll immediately if possible 
            if (!itemsControl.TryScrollToCenterOfView(item))
            {
                // Otherwise wait until everything is loaded, then scroll 
                if (itemsControl is ListBox) ((ListBox)itemsControl).ScrollIntoView(item);
                itemsControl.Dispatcher.BeginInvoke( new Action(() =>
                {
                    itemsControl.TryScrollToCenterOfView(item);
                }));
            }
        }

        private static bool TryScrollToCenterOfView(this ItemsControl itemsControl, object item)
        {
            // Find the container 
            var container = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
            if (container == null) return false;

            // Find the ScrollContentPresenter 
            ScrollContentPresenter presenter = null;
            for (UIElement vis = container; vis != null ; vis = VisualTreeHelper.GetParent(vis) as UIElement)
                if ((presenter = vis as ScrollContentPresenter) != null)
                    break;
            if (presenter == null) return false;

            // Find the IScrollInfo 
            var scrollInfo =
                !presenter.CanVerticallyScroll ? presenter :
                presenter.Content as IScrollInfo ??
                FirstVisualChild(presenter.Content as ItemsPresenter) as IScrollInfo ??
                presenter;

            // Compute the center point of the container relative to the scrollInfo 
            Size size = container.RenderSize;
            Point center = container.TransformToVisual((UIElement)scrollInfo).Transform(new Point(size.Width / 2, size.Height / 2));
            center.Y += scrollInfo.VerticalOffset;
            center.X += scrollInfo.HorizontalOffset;

            // Adjust for logical scrolling 
            if (scrollInfo is StackPanel || scrollInfo is VirtualizingStackPanel)
            {
                double logicalCenter = itemsControl.ItemContainerGenerator.IndexFromContainer(container) + 0.5;
                Orientation orientation = scrollInfo is StackPanel ? ((StackPanel)scrollInfo).Orientation : ((VirtualizingStackPanel)scrollInfo).Orientation;
                if (orientation == Orientation.Horizontal)
                    center.X = logicalCenter;
                else
                    center.Y = logicalCenter;
            }

            // Scroll the center of the container to the center of the viewport 
            if (scrollInfo.CanVerticallyScroll) scrollInfo.SetVerticalOffset(CenteringOffset(center.Y, scrollInfo.ViewportHeight, scrollInfo.ExtentHeight));
            if (scrollInfo.CanHorizontallyScroll) scrollInfo.SetHorizontalOffset(CenteringOffset(center.X, scrollInfo.ViewportWidth, scrollInfo.ExtentWidth));
            return true;
        }

        private static double CenteringOffset(double center, double viewport, double extent)
        {
            return Math.Min(extent - viewport, Math.Max(0, center - viewport / 2));
        }

        private static DependencyObject FirstVisualChild(UIElement visual)
        {
            if (visual == null) return null;
            if (VisualTreeHelper.GetChildrenCount(visual) == 0) return null;
            return VisualTreeHelper.GetChild(visual, 0);
        }
    } 
9
Scrappydog

L'exemple ci-dessous trouvera le scrollviewer de la listview et l'utilisera pour faire défiler l'élément jusqu'au milieu de la listview.

XAML:

<Window x:Class="ScrollIntoViewTest.Window1"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Height="300" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <ListView Grid.Row="0" ItemsSource="{Binding Path=Data}" Loaded="OnListViewLoaded"/>
        <ComboBox Grid.Row="1" ItemsSource="{Binding Path=Data}" SelectionChanged="OnScrollIntoView" />
    </Grid>
</Window>

Code derrière:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace ScrollIntoViewTest
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Data = new List<string>();
            for (int i = 0; i < 100; i++)
            {
                Data.Add(i.ToString());    
            }

            DataContext = this;
        }

        public List<string> Data { get; set; }

        private void OnListViewLoaded(object sender, RoutedEventArgs e)
        {
            // Assumes that the listview consists of a scrollviewer with a border around it
            // which is the default.
            Border border = VisualTreeHelper.GetChild(sender as DependencyObject, 0) as Border;
            _scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer;
        }

        private void OnScrollIntoView(object sender, SelectionChangedEventArgs e)
        {
            string item = (sender as ComboBox).SelectedItem as string;
            double index = Data.IndexOf(item) - Math.Truncate(_scrollViewer.ViewportHeight / 2);
            _scrollViewer.ScrollToVerticalOffset(index);
        }

        private ScrollViewer _scrollViewer;
    }
}
1

L'excellente réponse de Ray Burns ci-dessus et le commentaire de Fyodor Soikin:

"En fait, cela ne fonctionne avec aucun autre ItemsControl ... ne fonctionne pas avec DataGrid avec virtualisation activée ..."

Utilisation:

if (listBox.SelectedItem != null)
{
   listBox.ScrollIntoView(listBox.SelectedItem);
   listBox.ScrollToCenterOfView(listBox.SelectedItem);
}

@all: je ne peux pas commenter pour le moment, j'ai besoin de 50 points de réputation

1
Arc

J'ai trouvé une approche supplémentaire pour résoudre ce problème, en supposant que certains d'entre nous ont juste besoin d'un moyen de connaître la hauteur de l'élément visuel en fonction du modèle d'élément, cela vous ferait considérablement gagner du temps.

Ok, je suppose que votre XAML est structuré de façon similaire à ceci:

:
<Window.Resources>
   <DataTemplate x:Key="myTemplate">
      <UserControls1:myControl DataContext="{Binding}" />
   </DataTemplate>
</Window.Resources>
:
<ListBox Name="myListBox" ItemTemplate="{StaticResource ResourceKey=myTemplate}" />

Et vous voulez calculer pour faire défiler vers le centre, mais vous n'avez aucune idée de la hauteur actuelle de chaque élément dans votre listbox .. c'est ainsi que vous pouvez trouver:

listBoxItemHeight = (double)((DataTemplate)FindResource("myTemplate")).LoadContent().GetValue(HeightProperty);
1
G.Y

Je me souviens avoir fait quelque chose comme ça moi-même à un moment donné. Pour autant que je m'en souvienne, j'ai fait:

  1. Déterminez si l'objet est déjà visible ou non.
  2. S'il n'est pas visible, obtenez l'index de l'objet souhaité et le nombre d'objets actuellement affichés.
  3. (index you want) - (number of objects displayed / 2) devrait être la ligne du haut, faites défiler jusqu'à cela (assurez-vous de ne pas devenir négatif, bien sûr)
1
lc.

Si vous regardez le modèle d'une Listbox, c'est simplement un scrollviewer avec un itemspresenter à l'intérieur. Vous devrez calculer la taille de vos articles et utiliser scroll horizontalement ou verticalement pour positionner les articles dans votre scrollviewer. La boîte à outils d'avril silverlight a une méthode d'extension GetScrollHost que vous pouvez appeler sur une liste pour obtenir votre scrollviewer sous-jacent.

Une fois que vous avez cela, vous pouvez utiliser le décalage horizontal ou vertical actuel comme cadre de référence et déplacer votre liste en conséquence.

1
Ragepotato