2008-10-17 41 views
22

WPF, aplicación Browserlike.
Tengo una página que contiene un ListView. Después de llamar a un PageFunction añado una línea al ListView, y quiero para desplazarse por la nueva línea a la vista:Scroll WPF Listview a la línea específica

ListViewItem item = ItemContainerGenerator.ContainerFromIndex(index) as ListViewItem; 
    if (item != null) 
    ScrollIntoView(item); 

Esto funciona. Mientras la nueva línea esté a la vista, la línea se enfoca como debería.

El problema es que las cosas no funcionan cuando la línea no está visible.
Si la línea no está visible, no hay ListViewItem para la línea generada, por lo que ItemContainerGenerator.ContainerFromIndex devuelve null.

Pero sin el elemento, ¿cómo desplazo la línea hacia la vista? ¿Hay alguna manera de desplazarse a la última línea (o en cualquier lugar) sin necesidad de un ListViewItem?

+0

He utilizado tanto ListBox como DataGrid ScrollIntoView() y no presentan este problema? Es una pregunta tonta, pero ¿estás corriendo contra 3.5 SP1? Muchas cosas se arreglaron allí. –

+0

Sí, corro contra 3.5SP1, y encontré que esto no es un error. ListViewItem está virtualizado, lo cual está bien, pero ¿cómo puedo desplazarlo a la vista? – Sam

Respuesta

10

Creo que el problema aquí es que ListViewItem no se ha creado aún si la línea no está visible. WPF crea Visible a pedido.

Entonces, en este caso, probablemente obtenga null para el artículo, ¿o sí? (Según su comentario, lo hace)

He encontrado un link on MSDN forums that suggest accessing the Scrollviewer directly para desplazarse. Para mí, la solución presentada allí se parece mucho a un truco, pero puedes decidir por ti mismo.

Aquí es el fragmento de código de la link above:

VirtualizingStackPanel vsp = 
    (VirtualizingStackPanel)typeof(ItemsControl).InvokeMember("_itemsHost", 
    BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic, null, 
    _listView, null); 

double scrollHeight = vsp.ScrollOwner.ScrollableHeight; 

// itemIndex_ is index of the item which we want to show in the middle of the view 
double offset = scrollHeight * itemIndex_/_listView.Items.Count; 

vsp.SetVerticalOffset(offset); 
+0

Probé el código, funciona. No es lindo, pero una respuesta. Me gustaría aclarar mi pregunta para reflejar el problema real, si quieres incluir el código (los enlaces pueden cambiar) te marcaré como respuesta. – Sam

+0

Como funciona, te marqué como respuesta de todos modos, pero sigo pensando que sería bueno si el código se copia aquí, en caso de que el enlace cambie. – Sam

3

Una solución a esto es cambiar la ItemsPanel del ListView. El panel predeterminado es VirtualizingStackPanel que solo crea ListBoxItem la primera vez que se vuelven visibles. Si no tiene demasiados elementos en su lista, no debería ser un problema.

<ListView> 
    ... 
    <ListView.ItemsPanel> 
     <ItemsPanelTemplate> 
     <StackPanel/> 
     </ItemsPanelTemplate> 
    </ListView.ItemsPanel> 
</ListView> 
38

Alguien me dijo una mejor manera para desplazarse a una línea específica, que es fácil y funciona como encanto.
En resumen:

public void ScrollToLastItem() 
{ 
    lv.SelectedItem = lv.Items.GetItemAt(rows.Count - 1); 
    lv.ScrollIntoView(lv.SelectedItem); 
    ListViewItem item = lv.ItemContainerGenerator.ContainerFromItem(lv.SelectedItem) as ListViewItem; 
    item.Focus(); 
} 

La versión más larga en MSDN forums:

+0

Trabajos de ScrollIntoView - thx – Jeffrey

+0

El único problema con esto para mí es que anula crudamente cualquier selección que el usuario ya haya realizado. Aún así, debería ser una modificación bastante fácil de hacer, así que +1 para usted, señor, gracias. – metao

+0

Oh, sí, para mí, cambiar la selección era exactamente lo que quería, pero tienes razón. – Sam

2

Gracias por esa última punta Sam. Tuve un diálogo que se abrió, es decir, mi cuadrícula perdió el enfoque cada vez que se cerró el diálogo. Yo uso esto:

if(currentRow >= 0 && currentRow < lstGrid.Items.Count) { 
    lstGrid.SelectedIndex = currentRow; 
    lstGrid.ScrollIntoView(lstGrid.SelectedItem); 
    if(shouldFocusGrid) { 
     ListViewItem item = lstGrid.ItemContainerGenerator.ContainerFromItem(lstGrid.SelectedItem) as ListViewItem; 
     item.Focus(); 
    } 
} else if(shouldFocusGrid) { 
    lstGrid.Focus(); 
} 
5

Hice algunos cambios en la respuesta de Sam. Tenga en cuenta que quería desplazarme a la última línea. Por desgracia, el ListView sometiems solo está representada la última línea (incluso cuando había por ejemplo, 100 líneas por encima de ella), por lo que esta es la forma Me fijo que:

public void ScrollToLastItem() 
    { 
     if (_mainViewModel.DisplayedList.Count > 0) 
     { 
      var listView = myListView; 
      listView.SelectedItem = listView.Items.GetItemAt(_mainViewModel.DisplayedList.Count - 1); 
      listView.ScrollIntoView(listView.Items[0]); 
      listView.ScrollIntoView(listView.SelectedItem); 
      //item.Focus(); 
     } 
    } 

Saludos

2

probar este

private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) 
     { 
      ScrollViewer scrollViewer = GetScrollViewer(lstVw) as ScrollViewer; 
      scrollViewer.ScrollToHorizontalOffset(dataRowToFocus.RowIndex); 
      if (dataRowToFocus.RowIndex < 2) 
       lstVw.ScrollIntoView((Entity)lstVw.Items[0]); 
      else 
       lstVw.ScrollIntoView(e.AddedItems[0]); 
     } 

public static DependencyObject GetScrollViewer(DependencyObject o) 
     { 
      if (o is ScrollViewer) 
      { return o; } 

      for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++) 
      { 
       var child = VisualTreeHelper.GetChild(o, i); 

       var result = GetScrollViewer(child); 
       if (result == null) 
       { 
        continue; 
       } 
       else 
       { 
        return result; 
       } 
      } 
      return null; 
     } 

private void Focus() 
{ 
lstVw.SelectedIndex = dataRowToFocus.RowIndex; 
lstVw.SelectedItem = (Entity)dataRowToFocus.Row; 

ListViewItem lvi = (ListViewItem)lstVw.ItemContainerGenerator.ContainerFromItem(lstVw.SelectedItem); 
ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(lvi); 
contentPresenter.Focus(); 
contentPresenter.BringIntoView(); 

} 
+0

Esta es la respuesta perfecta, ya que puede mantener la virtualización y desplazarse a la fila específica, sin tener que lidiar con la selección original, incluso para listas de vista, que pueden tener diferentes alturas de elementos, como la mía (elementos expandibles). ¡Gracias! – lunatix

1

Acabo de tener el mismo problema con ItemContainerGenerator.ContainerFromItem() y ItemContainerGenerator.ContainerFromIndex() devolviendo null para los elementos que claramente existían en el cuadro de lista. Decasteljau tenía razón, pero tuve que investigar un poco para entender exactamente a qué se refería.Aquí está el desglose para salvar al próximo chico/gal un poco de trabajo de campo.

Para resumir, ListBoxItems se destruyen si no están a la vista. En consecuencia, ContainerFromItem() y ContainerFromIndex() devuelven nulo, ya que ListBoxItems no existe. Esto es aparentemente una función de ahorro de memoria/rendimiento detallada aquí: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo-1-virtualizingstackpanel-vs-stackpanel-as-a-listbox-itemspanel.aspx

El código vacío <ListBox.ItemsPanel> es lo que desactiva la virtualización. código de ejemplo que fija el problema para mí:

plantilla de datos:

<phone:PhoneApplicationPage.Resources> 
    <DataTemplate x:Key="StoryViewModelTemplate"> 
     <StackPanel> 
      <your datatemplated stuff here/> 
     </StackPanel> 
    </DataTemplate> 
</phone:PhoneApplicationPage.Resources> 

cuerpo principal:

<Grid x:Name="ContentPanel"> 
    <ListBox Name="lbResults" ItemsSource="{Binding SearchResults}" ItemTemplate="{StaticResource StoryViewModelTemplate}"> 
     <ListBox.ItemsPanel> 
      <ItemsPanelTemplate> 
       <StackPanel> 
       </StackPanel> 
      </ItemsPanelTemplate> 
     </ListBox.ItemsPanel> 
    </ListBox> 
</Grid> 
+0

¡Tenga en cuenta que la respuesta de Ray inhabilita la virtualización! –

2

Si lo que desea es mostrar y enfocar el último elemento después de crear un nuevo elemento de datos , este método es quizás mejor Comparar con ScrollIntoView, ScrollTo End de ScrollViewer es en mis pruebas más confiable. En algunas pruebas, el método ScrollIntoView de ListView como el anterior falló y no sé por qué. Pero usar ScrollViewer para desplazarse hasta el último puede funcionar.

void FocusLastOne(ListView lsv) 
{ 
    ObservableCollection<object> items= sender as ObservableCollection<object>; 

    Decorator d = VisualTreeHelper.GetChild(lsv, 0) as Decorator; 
    ScrollViewer v = d.Child as ScrollViewer; 
    v.ScrollToEnd(); 

    lsv.SelectedItem = lsv.Items.GetItemAt(items.Count - 1); 
    ListViewItem lvi = lsv.ItemContainerGenerator.ContainerFromIndex(items.Count - 1) as ListViewItem; 
    lvi.Focus(); 
} 
+2

lo siento, el primer claus debe ser: ObservableCollection items = lsv.ItemsSource como ObservableCollection ; –

+0

Puede editar su publicación presionando el botón Editar –

+0

Edite su respuesta en lugar de comentarla, por favor. Cheers, –

0

para superar el problema de la virtualización, pero todavía utilizan ScrollIntoView y no en torno a la piratería en las tripas del ListView, también se podría utilizar sus objetos ViewModel para determinar lo que se ha seleccionado. Suponiendo que tiene objetos ViewModel en su lista que cuentan con una propiedad IsSelected. Se podría enlazar los elementos a ListView en XAML como esto:

<ListView Name="PersonsListView" ItemsSource="{Binding PersonVMs}"> 
    <ListView.ItemContainerStyle> 
    <Style TargetType="{x:Type ListViewItem}"> 
     <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
    </Style> 
    </ListView.ItemContainerStyle> 
</ListView> 

A continuación, el código subyacente método puede desplazarse hasta el primer elemento seleccionado con esto:

var firstSelected = PersonsListView.Items 
    .OfType<TreeViewItemViewModel>().FirstOrDefault(x => x.IsSelected); 
if (firstSelected != null) 
    CoObjectsListView.ScrollIntoView(firstSelected); 

Esto también funciona si el el elemento seleccionado está fuera de la vista. En mi experimento, la propiedad PersonsListView.SelectedItem era null, pero por supuesto su propiedad ViewModel IsSelected siempre está ahí. Asegúrese de llamar a este método después de que se haya completado todo el enlace y la carga (con el DispatcherPriority derecho).

Utilizando el patrón ViewCommand, su código de modelo de vista podría tener este aspecto:

PersonVMs.ForEach(vm => vm.IsSelected = false); 
PersonVMs.Add(newPersonVM); 
newPersonVM.IsSelected = true; 
ViewCommandManager.InvokeLoaded("ScrollToSelectedPerson"); 
0

En mi proyecto i necesidad de mostrar la línea de índice seleccionado de la vista de lista para el usuario, así que le asigna el elemento seleccionado en el control ListView . Este código desplazará la barra de desplazamiento y mostrará el elemento seleccionado.

BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);

O

var Listview = BooleanListView; listView.SelectedItem = listView.Items.GetItemAt (BooleanListView.SelectedIndex); listView.ScrollIntoView (listView.Items [0]); listView.ScrollIntoView (listView.SelectedItem);

0

no estoy seguro de si este es el camino a seguir, pero esto actualmente funciona para mí con WPF, MVVM Light y .NET 3.5

he añadido el evento SelectionChanged para ListBox denominado "lbPossibleError_SelectionChanged" I added the SelectionChanged event for ListBox

continuación, detrás de esta "lbPossibleError_SelectionChanged" evento, aquí está el código enter image description here

funciona como debería para mí.