簡體   English   中英

如何在 WPF 數據網格上自動滾動

[英]How to autoscroll on WPF datagrid

我覺得我很傻。 我現在搜索了 15 分鍾,並找到了幾種不同的在數據網格上滾動的解決方案,但似乎沒有一個對我有用。

我將 WPF 與 .NET 3.5 和 WPF Toolkit DataGrid 一起使用。 當我的可觀察集合發生變化時,我的網格會更新,完美運行。 現在,我的 DataGrid 位於普通 Grid 內,如果 DataGrid 太大,則會出現滾動條。 也不錯...

現在是 1.000.000 美元的問題:

如何讓數據網格滾動到最后一行? 有:

  • 沒有 AutoScroll 屬性
  • 沒有 CurrentRowSelected 索引
  • 一個 CurrentCell,但沒有我可以用於 CurrentCell = AllCells.Last 的集合

有任何想法嗎? 我真的覺得自己很傻,這個問題這么難似乎也很奇怪。 我錯過了什么?

您應該使用數據網格方法

datagrid.ScrollIntoView(itemInRow);

要么

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

我為網格自動滾動編寫了一個附加屬性:

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

添加 AutoScroll To the Last 元素:

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

可能會有所幫助:)

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

我知道這是一個遲到的答案,但僅對於正在搜索的人來說,我找到了滾動到 DataGrid 底部的最簡單方法。 DataContextChanged事件中放入:

myDataGrid.ScrollIntoView(CollectionView.NewItemPlaceholder);

容易吧?

這就是它起作用的原因:在每個數據網格上,DataGrid 底部都有一個位置,您可以在其中將新項目添加到它所綁定到的列表中。 那是一個CollectionView.NewItemPlaceholder ,並且您的 DataGrid 中只會有一個。 所以你可以滾動到那個。

如果大數據 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(); 
            } 
        } 

我發現最簡單的方法是從 ScrollViewer.ScrollChanged 附加事件調用ScrollIntoView方法。 這可以在 XAML 中設置如下:

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

ScrollChangedEventArgs對象具有各種有助於計算布局和滾動位置(Extent、Offset、Viewport)的屬性。 請注意,當使用默認的 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]);
    }

實際上...

當我學習在 WPF 中執行 DataContext 的集合視圖時,我也遇到了同樣的問題。

我也面臨着將 WPF 程序拼湊在一起的任務,我需要以編程方式使用按鈕在 DataGrid 上上下移動,因為我需要將它放在電阻式觸摸屏上,僅適用於我的公司的生產制造商,還有沒有鼠標或鍵盤供他們使用。

但是這個例子使用ScrollIntoView方法對我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);
    }

其中 Orders 是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 的 Row Definition(在本例中為第三行),我將 Height 設置為 150,並且滾動條有效。

這是另一個很好的解決方案。

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

您需要的是獲取對 DataGrid 的 ScrollViewer 對象的引用。 然后您可以操縱 VerticalOffset 屬性滾動到底部。

要向您的應用程序添加更多光暈……您可以向滾動添加樣條動畫,以便一切看起來都與應用程序的其余部分相匹配。

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的方式來做到這一點?

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

以下代碼對我有用;

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

如果您正在尋找執行自動滾動的 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>

當您將 datagridview 與滾動條一起使用時,必須使用此技術。 因為我正在嘗試其他技術。 但那些不能正常工作......

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

如果您使用的是MVVM模式,則可以將本文與其他文章結合使用: http : //www.codeproject.com/KB/WPF/AccessControlsInViewModel.aspx

這個想法是使用附加屬性來訪問 ViewModel 類中的控件。 一旦你這樣做了,你需要檢查數據網格不為空,並且它有任何項目。

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM