我有一个DataGrid与许多项目,我需要以编程方式滚动到SelectedItem.我在StackOverflow和Google上搜索,似乎解决方案是ScrollIntoView,如下所示:
- grid.ScrollIntoView(grid.SelectedItem)
它会向上或向下滚动DataGrid,直到所选项目对焦.但是,根据当前相对于所选项目的滚动位置,所选项可能最终成为DataGrid的ScrollViewer中最后一个可见项.我希望所选项目将是ScrollViewer中的第一个可见项目(假设DataGrid中有足够的行可以允许).所以我试过这个:
- '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的结尾很好,但随后滚动到所选项目并不总是奏效.
如何解决这个问题,还有其他替代策略可以滚动到顶部位置的特定记录?
你在正确的轨道上,只是尝试使用集合视图,而不是直接在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>