web-dev-qa-db-fra.com

Comment faire défiler automatiquement la grille de données WPF

Je pense que je suis stupide. J'ai cherché pendant 15 minutes et ai trouvé plusieurs solutions différentes pour faire défiler des datagrids, mais aucune ne semble fonctionner pour moi.

J'utilise WPF avec .NET 3.5 et le WPF Toolkit DataGrid. Ma grille est mise à jour lorsque ma collection observable change et fonctionne parfaitement. Maintenant, mon DataGrid est situé dans une grille normale et des barres de défilement apparaissent si le DataGrid devient trop gros. Aussi très bien...

Et maintenant vient la question de 1.000.000 $:

Comment faire en sorte que la grille de données défile jusqu'à la dernière ligne? Il y a:

  • no AutoScroll Property
  • aucun index CurrentRowSelected
  • un CurrentCell, mais pas de Collection que je pourrais utiliser pour CurrentCell = AllCells.Last

Des idées? Je me sens vraiment stupide et il semble étrange que cette question soit si difficile. Qu'est-ce que je rate?

32
Christian Ruppert

;) 

if (mainDataGrid.Items.Count > 0)
{
    var border = VisualTreeHelper.GetChild(mainDataGrid, 0) as Decorator;
    if (border != null)
    {
        var scroll = border.Child as ScrollViewer;
        if (scroll != null) scroll.ScrollToEnd();
    }
}
41

Vous devriez utiliser la méthode datagrid

datagrid.ScrollIntoView(itemInRow);

ou

datagrid.ScrollIntoView(itemInRow, column);

de cette façon, vous ne perdez pas votre temps à trouver le visualiseur de défilement, etc.

48
Aran Mulholland

J'ai écrit une propriété attachée pour autoscroll de grille:

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

public static class DataGridBehavior
{
    public static readonly DependencyProperty AutoscrollProperty = DependencyProperty.RegisterAttached(
        "Autoscroll", typeof(bool), typeof(DataGridBehavior), new PropertyMetadata(default(bool), AutoscrollChangedCallback));

    private static readonly Dictionary<DataGrid, NotifyCollectionChangedEventHandler> handlersDict = new Dictionary<DataGrid, NotifyCollectionChangedEventHandler>();

    private static void AutoscrollChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
    {
        var dataGrid = dependencyObject as DataGrid;
        if (dataGrid == null)
        {
            throw new InvalidOperationException("Dependency object is not DataGrid.");
        }

        if ((bool)args.NewValue)
        {
            Subscribe(dataGrid);
            dataGrid.Unloaded += DataGridOnUnloaded;
            dataGrid.Loaded += DataGridOnLoaded;
        }
        else
        {
            Unsubscribe(dataGrid);
            dataGrid.Unloaded -= DataGridOnUnloaded;
            dataGrid.Loaded -= DataGridOnLoaded;
        }
    }

    private static void Subscribe(DataGrid dataGrid)
    {
        var handler = new NotifyCollectionChangedEventHandler((sender, eventArgs) => ScrollToEnd(dataGrid));
        handlersDict.Add(dataGrid, handler);
        ((INotifyCollectionChanged)dataGrid.Items).CollectionChanged += handler;
        ScrollToEnd(dataGrid);
    }

    private static void Unsubscribe(DataGrid dataGrid)
    {
        NotifyCollectionChangedEventHandler handler;
        handlersDict.TryGetValue(dataGrid, out handler);
        if (handler == null)
        {
            return;
        }
        ((INotifyCollectionChanged)dataGrid.Items).CollectionChanged -= handler;
        handlersDict.Remove(dataGrid);
    }

    private static void DataGridOnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        var dataGrid = (DataGrid)sender;
        if (GetAutoscroll(dataGrid))
        {
            Subscribe(dataGrid);
        }
    }

    private static void DataGridOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
        var dataGrid = (DataGrid)sender;
        if (GetAutoscroll(dataGrid))
        {
            Unsubscribe(dataGrid);
        }
    }

    private static void ScrollToEnd(DataGrid datagrid)
    {
        if (datagrid.Items.Count == 0)
        {
            return;
        }
        datagrid.ScrollIntoView(datagrid.Items[datagrid.Items.Count - 1]);
    }

    public static void SetAutoscroll(DependencyObject element, bool value)
    {
        element.SetValue(AutoscrollProperty, value);
    }

    public static bool GetAutoscroll(DependencyObject element)
    {
        return (bool)element.GetValue(AutoscrollProperty);
    }
}

Usage:

    <DataGrid c:DataGridBehavior.Autoscroll="{Binding AutoScroll}"/>
18
Denis Susloparov

Je sais que la réponse est tardive, mais je n'ai trouvé que LE moyen le plus facile de faire défiler les informations jusqu'au bas d'un DataGrid. dans l'événement DataContextChanged, mettez ceci dans:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

Facile hein?

C’est pourquoi cela fonctionne: sur chaque grille de données, il existe un emplacement au bas du DataGrid où vous pouvez ajouter un nouvel élément à votre liste auquel il est lié. C’est un CollectionView.NewItemPlaceholder, et il n’y aura qu’un de ceux-ci dans votre DataGrid. Donc, vous pouvez simplement faire défiler jusqu'à cela.

6
James Esh
listbox.Add(foo);
listbox.SelectedIndex = count - 1;
listbox.ScrollIntoView(listbox.SelectedItem);
listbox.SelectedIndex = -1;
6
azze

Pour avoir ajouté un élément AutoScroll jusqu'au dernier:

YourDataGrid.ScrollIntoView(YourDataGrid.Items.GetItemAt(YourDataGrid.Items.Count-1));

Peut cette aide :)

6
Anas

si données volumineuses datagrid.ScrollIntoView (itemInRow, column); fonctionne pas bien alors nous devons utiliser ci-dessous un seul:

if (mainDataGrid.Items.Count > 0) 
        { 
            var border = VisualTreeHelper.GetChild(mainDataGrid, 0) as Decorator; 
            if (border != null) 
            { 
                var scroll = border.Child as ScrollViewer; 
                if (scroll != null) scroll.ScrollToEnd(); 
            } 
        } 
3
TRS Rao

J'ai constaté que le moyen le plus simple de procéder consiste à appeler la méthode ScrollIntoView à partir de l'événement attaché ScrollViewer.ScrollChanged. Ceci peut être défini en XAML comme suit:

<DataGrid
...
ScrollViewer.ScrollChanged="control_ScrollChanged">

L'objet ScrollChangedEventArgs a diverses propriétés qui peuvent être utiles pour calculer la disposition et la position de défilement (étendue, décalage, fenêtre d'affichage). Notez que ceux-ci sont généralement mesurés en nombre de lignes/colonnes lorsque vous utilisez les paramètres de virtualisation DataGrid par défaut.

Voici un exemple d'implémentation qui maintient l'élément inférieur en vue lorsque de nouveaux éléments sont ajoutés au DataGrid, sauf si l'utilisateur déplace la barre de défilement pour afficher les éléments plus haut dans la grille.

    private void control_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        // If the entire contents fit on the screen, ignore this event
        if (e.ExtentHeight < e.ViewportHeight)
            return;

        // If no items are available to display, ignore this event
        if (this.Items.Count <= 0)
            return;

        // If the ExtentHeight and ViewportHeight haven't changed, ignore this event
        if (e.ExtentHeightChange == 0.0 && e.ViewportHeightChange == 0.0)
            return;

        // If we were close to the bottom when a new item appeared,
        // scroll the new item into view.  We pick a threshold of 5
        // items since issues were seen when resizing the window with
        // smaller threshold values.
        var oldExtentHeight = e.ExtentHeight - e.ExtentHeightChange;
        var oldVerticalOffset = e.VerticalOffset - e.VerticalChange;
        var oldViewportHeight = e.ViewportHeight - e.ViewportHeightChange;
        if (oldVerticalOffset + oldViewportHeight + 5 >= oldExtentHeight)
            this.ScrollIntoView(this.Items[this.Items.Count - 1]);
    }
2
Matt

Réellement...

J'ai eu le même problème lorsque j'apprenais à propos de Collection Views comment utiliser DataContext dans WPF. 

Moi aussi, j’ai été confronté à la tâche de coordonner un programme WPF dont j’ai besoin pour pouvoir monter et descendre sur le DataGrid à l’aide de boutons, car je devais le placer sur un écran tactile résistif UNIQUEMENT pour les constructeurs de production pas de souris ni de clavier à utiliser.

Mais cet exemple a fonctionné pour moi en utilisant la méthode ScrollIntoView comme mentionné précédemment dans ce post:

    private void OnMoveUp(object sender, RoutedEventArgs e)
    {
        ICollectionView myCollectView = CollectionViewSource.GetDefaultView(Orders);
        if (myCollectView.CurrentPosition > 0)
            myCollectView.MoveCurrentToPrevious();

        if (myCollectView.CurrentItem != null)
            theDataGrid.ScrollIntoView(myCollectView.CurrentItem);
    }

    private void OnMoveDown(object sender, RoutedEventArgs e)
    {
        ICollectionView  myCollectView = CollectionViewSource.GetDefaultView(Orders);
        if (myCollectView.CurrentPosition < Orders.Count)
            myCollectView.MoveCurrentToNext();

        if (myCollectView.CurrentItem !=null)
            theDataGrid.ScrollIntoView(myCollectView.CurrentItem);
    }

Where Orders est une collection List<T>

en XAML:

    <StackPanel Grid.Row="1"
        Orientation="Horizontal">
            <Button Click="OnMoveUp">
                <Image Source="Up.jpg" />
            </Button>
            <Button Click="OnMoveDown">
                <Image Source="Down.jpg" />
              </Button>
    </StackPanel>

    <DataGrid Grid.Row="2"
              x:Name="theDataGrid"
              ItemSource="{Binding Orders}"
              ScrollViewer.CanContentScroll="True"
              ScrollViewer.VerticalScrollBarVisibility="Auto" Margin="0,0,0,5">

    << code >>


    </DataGrid>

Suivez les conseils précédents et conservez le DataGrid seul et non dans un panneau de pile. Pour la définition de ligne pour DataGrid (la troisième ligne dans ce cas), je règle la hauteur sur 150 et la barre de défilement fonctionne.

1
Steve Brother

Voici une autre excellente solution.

public sealed class CustomDataGrid : DataGrid
{
    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
    {
        base.OnItemsSourceChanged(oldValue, newValue);
    }
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
    {
        base.OnItemsChanged(e);
        if (this.Items.Count > 0) this.ScrollIntoView(this.Items[this.Items.Count - 1]);
    }
}
1
James M

Ce dont vous avez besoin, c'est d'obtenir la référence à l'objet ScrollViewer pour votre DataGrid. Vous pouvez ensuite manipuler la propriété VerticalOffset pour faire défiler vers le bas.

Pour ajouter encore plus de lumière à votre application ... vous pouvez ajouter une animation Spline au défilement de sorte que tout se compare au reste de l'application.

0
Justin Niessner

Le code suivant fonctionne pour moi;

Private Sub DataGrid1_LoadingRow(sender As Object, e As DataGridRowEventArgs) Handles DataGrid1.LoadingRow
    DataGrid1.ScrollIntoView(DataGrid1.Items.GetItemAt(DataGrid1.Items.Count - 1))
End Sub
0
Mark Markowitz

Si vous utilisez le modèle MVVM, vous pouvez combiner cet article avec cet autre: http://www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx .

L'idée est d'utiliser des propriétés attachées pour accéder au contrôle dans votre classe ViewModel. Une fois que vous avez fait cela, vous devez vérifier que la grille de données n’est pas nulle et qu’elle contient des éléments.

if ((mainDataGrid != null) && (mainDataGrid.Items.Count > 0)){
//Same snippet
}
0
PILuaces

Si vous avez utilisé dataview pour le datagrid.datacontext, vous pouvez utiliser ceci:

private void dgvRecords_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    var dv = dgvRecords.DataContext as DataView;
    if (dv.Count > 0)
    {
        var drv = dv[dv.Count - 1] as DataRowView;
        dgvRecords.ScrollIntoView(drv);
    }
}
0
Peter Tran

Défilement automatique WPF DataGrid

Défilement automatique tant que le bouton de la souris est enfoncé sur une commande de bouton.

Le XAML

<Button x:Name="XBTNPageDown" Height="50" MouseLeftButtonDown="XBTNPageDown_MouseLeftButtonDown"  MouseUp="XBTNPageDown_MouseUp">Page Down</Button>

Le code

    private bool pagedown = false;
    private DispatcherTimer pageDownTimer = new DispatcherTimer();

    private void XBTNPageDown_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        pagedown = true;
        pageDownTimer.Interval = new TimeSpan(0, 0, 0, 0, 30);
        pageDownTimer.Start();
        pageDownTimer.Tick += (o, ea) =>
        {
            if (pagedown)
            {
                var sv = XDG.FindVisualChild<ScrollViewer>();
                sv.PageDown();
                pageDownTimer.Start();
            }
            else
            {
                pageDownTimer.Stop();
            }
        };
    }

    private void XBTNPageDown_MouseUp(object sender, MouseButtonEventArgs e)
    {
        pagedown = false;
    }

C'est la méthode d'extension

Placez-le dans une classe statique de votre choix et ajoutez une référence au code ci-dessus.

   public static T FindVisualChild<T>(this 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 = FindVisualChild<T>(child);
                if (childItem != null) return childItem;
            }
        }
        return null;
    }

NOTE: La propriété sv peut être déplacée pour éviter le travail répété. 

Quelqu'un at-il un moyen de faire cela RX?

0
John Peters