studio - Desplácese WPF DataGrid para mostrar el elemento seleccionado en la parte superior
visual studio wpf (3)
Tengo un DataGrid con muchos elementos y necesito desplazarme programáticamente al SelectedItem
. He buscado en StackOverflow y Google, y parece que la solución es ScrollIntoView, de la siguiente manera:
grid.ScrollIntoView(grid.SelectedItem)
que desplaza el DataGrid hacia arriba o hacia abajo hasta que el elemento seleccionado esté enfocado. Sin embargo, dependiendo de la posición de desplazamiento actual relativa al elemento seleccionado, el elemento seleccionado puede terminar siendo el último elemento visible en el ScrollViewer de DataGrid. Quiero que el elemento seleccionado sea el primer elemento visible en ScrollViewer (suponiendo que haya suficientes filas en DataGrid para permitir esto). Así que probé esto:
''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)
Primero me desplazo hasta el final de DataGrid y solo entonces me desplazo al SelectedItem, en cuyo punto se muestra SelectedItem en la parte superior de DataGrid.
Mi problema es que desplazarse hasta el final de DataGrid funciona bien, pero el desplazamiento posterior al elemento seleccionado no siempre funciona.
¿Cómo puedo resolver este problema, o hay alguna otra estrategia alternativa para desplazarse a un registro específico en la posición superior?
Estaba en el camino correcto, simplemente intente trabajar con la vista de colección en lugar de trabajar directamente en la cuadrícula de datos para este tipo de necesidades.
Aquí hay un ejemplo de trabajo donde siempre se muestra el elemento deseado como el primer elemento seleccionado, de lo contrario, el desplazador de desplazamiento se desplaza hasta el final y el elemento de destino se selecciona en su posición.
Los puntos clave son:
- Use CollectionView en el lado comercial y habilite la sincronización actual de elementos en el control XAML (
IsSynchronizedWithCurrentItem=true
) - Postergue el desplazamiento del objetivo "real" para permitir que el "Elemento seleccionado del último" se ejecute en forma visual (utilizando un
Dispatcher.BeginInvoke
con baja prioridad)
Aquí está la lógica de negocios (Esta es la conversión automática de C # a 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
Y este es el 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>
La respuesta aceptada a esta otra pregunta muestra un enfoque diferente para obtener la primera / última fila visible de dicha cuadrícula. Puede encontrar el índice de su fila y desplazarse directamente hacia allí o desplazarse fila por fila hasta que coincida la primera fila visible.
Resolví esta pregunta con el siguiente código:
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>