web-dev-qa-db-de.com

Wie man auf WPF-Datagrid automatisch scrollt

Ich denke ich bin dumm. Ich habe jetzt 15 Minuten lang gesucht und verschiedene Lösungen für das Scrollen auf Datagrids gefunden, aber keine scheint für mich zu funktionieren.

Ich verwende WPF mit .NET 3.5 und dem WPF Toolkit DataGrid. Mein Raster wird aktualisiert, wenn sich meine beobachtbare Sammlung ändert. Es funktioniert perfekt. Jetzt befindet sich mein DataGrid in einem normalen Raster. Bildlaufleisten werden angezeigt, wenn das DataGrid zu groß wird. Auch gut...

Und jetzt kommt die 1.000.000 $ Frage:

Wie kann ich das Datagrid zur letzten Zeile scrollen lassen? Es gibt:

  • keine AutoScroll-Eigenschaft
  • kein CurrentRowSelected-Index
  • eine CurrentCell, aber keine Collection, die ich für CurrentCell = AllCells.Last verwenden könnte

Irgendwelche Ideen? Ich fühle mich wirklich dumm und es ist seltsam, dass diese Frage so schwer ist. Was vermisse ich?

32

;) 

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();
    }
}

Sie sollten die Datagrid-Methode verwenden

datagrid.ScrollIntoView(itemInRow);

oder

datagrid.ScrollIntoView(itemInRow, column);

auf diese Weise können Sie den Scroll-Viewer usw. nicht finden.

48
Aran Mulholland

Ich habe eine angefügte Eigenschaft für die Rastersynchronisierung geschrieben:

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);
    }
}

Verwendungszweck:

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

Ich weiß, dass dies eine späte Antwort ist, aber nur für die Leute, die sich umsehen, habe ich DIE EINFACHSTE Möglichkeit gefunden, zum Ende eines DataGrid zu scrollen. Geben Sie im Ereignis DataContextChanged Folgendes ein:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

Leicht, nicht wahr?

Deshalb funktioniert es: Auf jedem Datenraster gibt es eine Stelle am unteren Rand des DataGrid, an der Sie Ihrer Liste ein neues Element hinzufügen können, an das es gebunden ist. Das ist ein CollectionView.NewItemPlaceholder, und es wird nur einen davon in Ihrem DataGrid geben. Sie können also einfach dazu blättern.

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

Für ein AutoScroll To Last-Element wurde Folgendes hinzugefügt:

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

Möge diese Hilfe :)

6
Anas

wenn große Daten datagrid.ScrollIntoView (itemInRow, column); funktioniert nicht gut, dann müssen wir nur eine davon verwenden:

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

Ich habe herausgefunden, dass der einfachste Weg, dies zu tun, darin besteht, die ScrollIntoView - Methode aus dem angehängten Ereignis ScrollViewer.ScrollChanged aufzurufen. Dies kann in XAML wie folgt festgelegt werden:

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

Das ScrollChangedEventArgs -Objekt verfügt über verschiedene Eigenschaften, die bei der Berechnung des Layouts und der Bildlaufposition hilfreich sein können (Ausdehnung, Versatz, Ansichtsfenster). Beachten Sie, dass diese normalerweise in Zeilen/Spalten gemessen werden, wenn Sie die Standard-Virtualisierungseinstellungen von DataGrid verwenden.

Im Folgenden sehen Sie eine Beispielimplementierung, bei der das unterste Element in der Ansicht bleibt, wenn neue Elemente zum DataGrid hinzugefügt werden, es sei denn, der Benutzer verschiebt die Bildlaufleiste, um Elemente höher im Raster anzuzeigen.

    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

Tatsächlich...

Ich hatte das gleiche Problem, als ich über Collection Views etwas über DataContext in WPF lernte. 

Ich war auch mit der Aufgabe konfrontiert, ein WPF-Programm zusammenzuschlagen, das ich programmgesteuert verwenden muss, um das DataGrid mithilfe von Tasten auf und ab zu bewegen, da ich es NUR für die Produktionsbauer meines Unternehmens auf einen resistiven Touchscreen legen musste Keine Maus oder Tastatur für sie.

Dieses Beispiel funktionierte jedoch für mich mit der ScrollIntoView-Methode, wie zuvor in diesem Beitrag erwähnt:

    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 ist eine List<T>-Sammlung

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

Folgen Sie den vorherigen Hinweisen, und bewahren Sie das DataGrid allein und nicht in einem Stapelbereich auf. Für die Zeilendefinition für das DataGrid (die dritte Zeile in diesem Fall) habe ich die Höhe auf 150 festgelegt, und die Bildlaufleiste funktioniert.

1
Steve Brother

Hier ist eine weitere hervorragende Lösung.

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

Sie benötigen den Verweis auf das ScrollViewer-Objekt für Ihr DataGrid. Sie können dann die VerticalOffset-Eigenschaft bearbeiten, um nach unten zu scrollen.

Um Ihrer App noch mehr Flare hinzuzufügen, können Sie der Bildlaufleiste eine Spline-Animation hinzufügen, damit alles mit dem Rest der Anwendung übereinstimmt.

0
Justin Niessner

Der folgende Code funktioniert für mich.

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

Wenn Sie MVVM -Muster verwenden, können Sie eine Kombination dieses Artikels mit diesem anderen haben: http://www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx .

Die Idee ist, angefügte Eigenschaften zu verwenden, um auf das Steuerelement in Ihrer ViewModel-Klasse zuzugreifen. Wenn Sie dies getan haben, müssen Sie überprüfen, ob das Datagrid nicht null ist und Elemente enthält.

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

Wenn Sie dataview für den Datagrid.datacontext verwendet haben, können Sie Folgendes verwenden:

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

WPF DataGrid Auto Scrolling

Automatisches Scrollen, solange sich die Maustaste auf einem Tastenfeld befindet.

Die XAML

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

Der 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;
    }

Dies ist die Erweiterungsmethode

Platzieren Sie es in einer statischen Klasse Ihrer Wahl und fügen Sie einen Verweis auf den obigen Code hinzu.

   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;
    }

HINWEIS: Die Eigenschaft sv kann verschoben werden, um wiederholte Arbeit zu vermeiden. 

Hat jemand eine Möglichkeit, dies zu tun?

0
John Peters