2016-12-28 58 views
10

我有一個DataGrid有很多項目,我需要以編程方式滾動到SelectedItem。我已經搜索StackOverflow上和谷歌,似乎解決的辦法是ScrollIntoView,如下:滾動WPF DataGrid顯示頂部的選定項目

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的末端工作正常,但隨後滾動到選定的項目並不總是工作。

我該如何解決這個問題,或者是否有任何其他策略滾動到頂部位置的特定記錄?

回答

3

this other question的接受答案顯示了獲取此類網格的第一個/最後一個可見行的不同方法。 您可以找到您的行的索引,並直接滾動或逐行向下滾動,直到第一個可見行匹配。

5

您在正確的軌道上,只是嘗試使用集合視圖而不是直接在數據網格上處理這種需求。

下面是一個工作示例,如果可能,所需項目始終顯示爲第一個選定項目,否則將滾動查看器滾動到結尾,並在其位置選擇目標項目。

的關鍵點是:

  • 使用的CollectionView在業務方面,使在XAML控制(IsSynchronizedWithCurrentItem=true
  • 推遲,以便讓「真正」的目標滾動當前項目同步「選擇最後一個項目」要被執行visualy(通過使用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> 
3

我解決了下面的代碼這個問題:

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>