滚动WPF datagrid在顶部显示所选项目



我有一个带有许多项目的datagrid,我需要编程滚动到 SelectedItem。我已经在Stackoverflow和Google上进行了搜索,似乎解决方案是Scrollintoview,如下:

grid.ScrollIntoView(grid.SelectedItem)

滚动数据杂志向上或向下滚动,直到所选项目焦点为止。但是,根据相对于所选项目的当前滚动位置,所选项目最终可能是DataGrid scrollviewer中的最后一个可见项目。我希望所选的项目将是ScrollViewer中的第一个可见项目(假设数据杂志中有足够的行以允许这样做)。所以我尝试了:

'FindVisualChild is a custom extension method that searches in the visual tree and returns 
'the first element of the specified type
Dim sv = grid.FindVisualChild(Of ScrollViewer)
If sv IsNot Nothing Then sv.ScrollToEnd()
grid.ScrollIntoView(grid.SelectedItem)

首先,我滚动到datagrid的末端,然后才滚动到SelectedItem,此时SelectedItem在DataGrid的顶部显示。

我的问题是滚动到数据杂志的末端效果很好,但是随后滚动到所选项目并不总是有效的。

我该如何解决此问题,或者还有其他替代策略可以滚动到顶部位置的特定记录?

您在正确的轨道上,只需尝试与收集视图一起工作,而不是直接在DataGrid上工作。

这是一个有效的示例,如果可能的话,始终将所需的项目显示为第一个选定的项目,否则将滚动浏览器滚动到末端,并且在其位置选择了目标项目。

关键点是:

  • 在业务方面使用CollectionView,并在XAML控件(IsSynchronizedWithCurrentItem=true)上启用当前项目同步
  • 推迟"真实"目标滚动,以允许执行"选择最后一项"(通过使用较低优先级的Dispatcher.BeginInvoke

这是业务逻辑(这是从C#到VB的自动转换)

Public Class Foo
    Public Property FooNumber As Integer
        Get
        End Get
        Set
        End Set
    End Property
End Class
Public Class MainWindow
    Inherits Window
    Implements INotifyPropertyChanged
    Private _myCollectionView As ICollectionView
    Public Sub New()
        MyBase.New
        DataContext = Me
        InitializeComponent
        MyCollection = New ObservableCollection(Of Foo)
        MyCollectionView = CollectionViewSource.GetDefaultView(MyCollection)
        Dim i As Integer = 0
        Do While (i < 50)
            MyCollection.Add(New Foo)
            i = (i + 1)
        Loop
    End Sub
    Public Property MyCollectionView As ICollectionView
        Get
            Return Me._myCollectionView
        End Get
        Set
            Me._myCollectionView = value
            Me.OnPropertyChanged("MyCollectionView")
        End Set
    End Property
    Private Property MyCollection As ObservableCollection(Of Foo)
        Get
        End Get
        Set
        End Set
    End Property
    Private Sub ButtonBase_OnClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim targetNum As Integer = Convert.ToInt32(targetScroll.Text)
        Dim targetObj As Foo = Me.MyCollection.FirstOrDefault(() => {  }, (r.FooNumber = targetNum))
        'THIS IS WHERE THE MAGIC HAPPENS
        If (Not (targetObj) Is Nothing) Then
            'Move to the collection view to the last item
            Me.MyCollectionView.MoveCurrentToLast
            'Bring this last item into the view
            Dim current = Me.MyCollectionView.CurrentItem
            itemsContainer.ScrollIntoView(current)
            'This is the trick : Invoking the real target item select with a low priority allows previous visual change (scroll to the last item) to be executed
            Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, New Action(() => {  }, Me.ScrollToTarget(targetObj)))
        End If
    End Sub
    Private Sub ScrollToTarget(ByVal targetObj As Foo)
        Me.MyCollectionView.MoveCurrentTo(targetObj)
        itemsContainer.ScrollIntoView(targetObj)
    End Sub
    Public Event PropertyChanged As PropertyChangedEventHandler
    Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
        If (Not (PropertyChanged) Is Nothing) Then
            PropertyChanged?.Invoke(Me, New PropertyChangedEventArgs(propertyName))
        End If
    End Sub
End Class

这是xaml

 <Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <DataGrid x:Name="itemsContainer" ItemsSource="{Binding MyCollectionView}" IsSynchronizedWithCurrentItem="True"  Margin="2" AutoGenerateColumns="False" >
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding FooNumber}"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
    <StackPanel Grid.Column="1">
        <TextBox x:Name="targetScroll" Text="2" Margin="2"></TextBox>
        <Button Content="Scroll To item" Click="ButtonBase_OnClick" Margin="2"></Button>
    </StackPanel>
</Grid>

我用以下代码解决了这个问题:

public partial class MainWindow:Window
{
    private ObservableCollection<Product> products=new ObservableCollection<Product> ();
    public MainWindow()
    {
        InitializeComponent ();
        for (int i = 0;i < 50;i++)
        {
            Product p=new Product { Name="Product "+i.ToString () };
            products.Add (p);
        }
        lstProduct.ItemsSource=products;
    }
    private void lstProduct_SelectionChanged(object sender,SelectionChangedEventArgs e)
    {
        products.Move (lstProduct.SelectedIndex,0);
        lstProduct.ScrollIntoView (lstProduct.SelectedItem);
    }
}
public class Product
{
    public string Name { get; set; }
}

<Grid>
    <ListBox Name="lstProduct" Margin="20" DisplayMemberPath="Name" SelectionChanged="lstProduct_SelectionChanged" />
</Grid>

对此问题的接受答案显示了一种不同的方法,即获得此类网格的第一个/最后一个可见行。您可以找到行的索引,然后直接滚动在此处,或者按行向下滚动,直到第一个可见行匹配为止。

最新更新