如何在WPF数据网格上自动滚动

时间:2009-06-22 12:51:59

标签: c# wpf datagrid

我觉得我很蠢。我现在搜索了15分钟,发现了几种不同的数据网格滚动解决方案,但似乎没有一个对我有用。

我正在使用WPF和.NET 3.5以及WPF Toolkit DataGrid。当我的可观察集合发生变化时,我的网格会更新,完美运行。现在,我的DataGrid位于普通网格内,如果DataGrid太大,则会出现滚动条。也很好......

现在来了1.000.000 $问题:

如何让datagrid滚动到最后一行? 有:

  • 没有AutoScroll属性
  • 没有CurrentRowSelected Index
  • 一个CurrentCell,但没有我可以用于CurrentCell = AllCells.Last
  • 的集合

有什么想法吗?我觉得自己真的很蠢,这个问题太难了似乎很奇怪。我错过了什么?

17 个答案:

答案 0 :(得分:48)

您应该使用datagrid方法

datagrid.ScrollIntoView(itemInRow);

datagrid.ScrollIntoView(itemInRow, column);

这种方式不会乱七八糟地找到滚动查看器等。

答案 1 :(得分:42)

;)

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

答案 2 :(得分:18)

我为网格自动滚动编写了附加属性:

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}"/>

答案 3 :(得分:7)

为了添加AutoScroll To Last元素:

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

愿这个帮助:)

答案 4 :(得分:6)

我知道这是一个迟到的答案,但只是为了搜索的人,我找到了最简单的方法来滚动到DataGrid的底部。在DataContextChanged事件中将其放入:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

好吧?

这就是它的工作原理:在每个数据网格上,DataGrid底部都有一个位置,您可以在其中将新项目添加到它所绑定的列表中。这是一个CollectionView.NewItemPlaceholder,只有一个在你的DataGrid中。所以你可以滚动到那个。

答案 5 :(得分:6)

listbox.Add(foo);
listbox.SelectedIndex = count - 1;
listbox.ScrollIntoView(listbox.SelectedItem);
listbox.SelectedIndex = -1;

答案 6 :(得分:3)

如果是大数据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(); 
            } 
        } 

答案 7 :(得分:2)

我发现最简单的方法是从ScrollViewer.ScrollChanged附加事件中调用ScrollIntoView方法。这可以在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]);
    }

答案 8 :(得分:1)

实际上...

当我在学习有关在WPF中执行DataContext的集合视图时,我也遇到了同样的问题。

我也面临着将WPF程序打包在一起的任务,我需要使用按钮在程序上对DataGrid进行上下移动,因为我需要将它放在电阻式触摸屏上,仅用于生产建设者\ t我的公司,并且没有鼠标或键盘供他们使用。

但是这个例子对我使用了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,滚动条可以正常工作。

答案 9 :(得分:1)

这是另一个出色的解决方案。

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

答案 10 :(得分:0)

如果您使用的是 MVVM 模式,则可以将本文与其他文章结合使用:http://www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx

我们的想法是使用附加属性来访问ViewModel类中的控件。一旦你这样做,你需要检查datagrid是否为null,并且它有任何项目。

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

答案 11 :(得分:0)

您需要的是获取DataGrid的ScrollViewer对象的引用。然后,您可以操纵VerticalOffset属性以滚动到底部。

要为您的应用添加更多耀斑......您可以在滚动条中添加样条曲线动画,以便所有内容都与应用程序的其余部分相同。

答案 12 :(得分:0)

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方式吗?

答案 13 :(得分:0)

如果您使用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);
    }
}

答案 14 :(得分:0)

以下代码对我有用;

.example {
  -webkit-transform: scale(1); /* Ch <36, Saf 5.1+, iOS < 9.2, An =<4.4.4 */
      -ms-transform: scale(1); /* IE 9 */
          transform: scale(1); /* IE 10, Fx 16+, Op 12.1+ */
}

答案 15 :(得分:0)

如果您正在寻找执行自动滚动的MVVM方式,则可以使用自动滚动行为。该行为滚动到选定的项目,只需添加对System.Windows.Interactivity.dll的引用:

public class ScrollIntoViewBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.SelectionChanged += new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
    }

    void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (sender is DataGrid)
        {
            DataGrid grid = (sender as DataGrid);
            if (grid?.SelectedItem != null)
            {
                grid.Dispatcher.InvokeAsync(() =>
                {
                    grid.UpdateLayout();
                    grid.ScrollIntoView(grid.SelectedItem, null);
                });
            }
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.SelectionChanged -=
            new SelectionChangedEventHandler(AssociatedObject_SelectionChanged);
    }
}

XAML

<DataGrid>
    <i:Interaction.Behaviors>
        <local:ScrollIntoViewBehavior/>
    </i:Interaction.Behaviors>
</DataGrid>

答案 16 :(得分:0)

在使用带有滚动条的datagridview时,必须使用此技术。因为我尝试了其他技术。但是那些不能正常工作。...

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