it-swarm.com.ru

Как выполнить автоматическую прокрутку на сетке данных WPF

Я думаю, что я глупый. Я искал 15 минут и нашел несколько разных решений для прокрутки данных, но ни одно из них мне не помогло.

Я использую WPF с .NET 3.5 и WPF Toolkit DataGrid. Моя сетка обновляется, когда моя наблюдаемая коллекция изменяется, работает отлично. Теперь моя DataGrid находится внутри обычной Grid, и полосы прокрутки появляются, если DataGrid становится слишком большим. Тоже все в порядке...

А теперь приходит вопрос 1.000.000 $:

Как получить сетку данных для прокрутки до последнего ряда? Есть:

  • нет свойства AutoScroll
  • нет CurrentRowSelected Index
  • currentCell, но нет коллекции, которую я мог бы использовать для CurrentCell = AllCells.Last

Есть идеи? Я чувствую себя действительно глупо, и кажется странным, что этот вопрос такой сложный. Что мне не хватает?

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
Joseph jun. Melettukunnel

Вы должны использовать метод datagrid

datagrid.ScrollIntoView(itemInRow);

или же

datagrid.ScrollIntoView(itemInRow, column);

этот способ не мешает найти средство просмотра прокрутки и т. д.

48
Aran Mulholland

Я написал прикрепленное свойство для сетки автопрокрутки:

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

Использование:

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

Я знаю, что это поздний ответ, но только для людей, которые ищут, я нашел самый простой способ прокрутки до нижней части DataGrid. в событии DataContextChanged поместите это в:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

Полегче, а?

Вот почему это работает: в каждой сетке данных есть место внизу DataGrid, где вы можете добавить новый элемент в свой список, к которому он привязан. Это CollectionView.NewItemPlaceholder, и в вашей DataGrid будет только один из них. Так что вы можете просто перейти к этому.

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

Для добавления элемента AutoScroll To Last:

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

Пусть это поможет :)

6
Anas

если большой объем данных, datagrid.ScrollIntoView (itemInRow, column); не работает нормально, то нам нужно использовать только один ниже:

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

Я обнаружил, что самый простой способ сделать это - вызвать метод ScrollIntoView из вложенного события ScrollViewer.ScrollChanged. Это может быть установлено в XAML следующим образом:

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

Объект ScrollChangedEventArgs имеет различные свойства, которые могут быть полезны для вычисления макета и положения прокрутки (экстент, смещение, область просмотра). Обратите внимание, что они обычно измеряются в количестве строк/столбцов при использовании настроек виртуализации DataGrid по умолчанию.

Вот пример реализации, которая сохраняет нижний элемент в поле зрения, когда новые элементы добавляются в DataGrid, если пользователь не перемещает полосу прокрутки для просмотра элементов выше в сетке.

    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

На самом деле...

У меня была такая же проблема, когда я узнал о коллекциях представлений о создании DataContext в WPF. 

Передо мной также стояла задача собрать воедино WPF-программу, которая мне нужна программно для перемещения вверх и вниз по DataGrid с помощью кнопок, поскольку мне нужно было поместить его на резистивный сенсорный экран ТОЛЬКО для строителей производства моей компании. нет мыши или клавиатуры для их использования.

Но этот пример работал для меня, используя метод ScrollIntoView, как упоминалось ранее в этом посте:

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

Где Заказы - это коллекция List<T>

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

Следуйте предыдущему совету и храните DataGrid отдельно, а не в панели стека. Для определения строки для DataGrid (в данном случае третьей строки) я установил Высота на 150, и полоса прокрутки работает.

1
Steve Brother

Вот еще одно отличное решение.

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

Вам нужно получить ссылку на объект ScrollViewer для вашей DataGrid. Затем вы можете манипулировать свойством VerticalOffset для прокрутки вниз.

Чтобы добавить еще больше бликов в ваше приложение ... вы можете добавить анимацию Spline к свитку, чтобы все выглядело на уровне остальных приложений.

0
Justin Niessner

Следующий код работает для меня;

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

Если вы используете шаблон MVVM, вы можете использовать комбинацию этой статьи с этим другим: http://www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx .

Идея состоит в том, чтобы использовать вложенные свойства для доступа к элементу управления в вашем классе ViewModel. Как только вы это сделаете, вам нужно будет проверить, что сетка данных не равна нулю, и в ней есть какие-либо элементы.

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

Если вы использовали dataview для datagrid.datacontext, вы можете использовать это:

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

Автопрокрутка до тех пор, пока кнопка мыши нажата на кнопке управления.

XAML

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

Код

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

Это метод расширения

Поместите его в статический класс по вашему выбору и добавьте ссылку на код выше.

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

ПРИМЕЧАНИЕ. Свойство sv можно переместить, чтобы избежать повторной работы. 

У кого-нибудь есть способ RX сделать это?

0
John Peters