Pregunta

WPF, aplicación tipo navegador.
Tengo una página que contiene un ListView.Después de llamar a PageFunction, agrego una línea a ListView y quiero desplazar la nueva línea a la vista:

  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 es visible.
Si la línea no es visible, no hay ningún ListViewItem para la línea generada, por lo que ItemContainerGenerator.ContainerFromIndex devuelve nulo.

Pero sin el elemento, ¿cómo puedo desplazar la línea para que aparezca a la vista?¿Hay alguna manera de desplazarse a la última línea (o a cualquier lugar) sin necesidad de un ListViewItem?

¿Fue útil?

Solución

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

Entonces, en este caso, probablemente obtenga null para el artículo, ¿verdad? (Según tu comentario, lo haces)

He encontrado un enlace en foros de MSDN que sugieren acceder directamente al Scrollviewer para desplazarse. Para mí, la solución presentada allí se parece mucho a un truco, pero puedes decidir por ti mismo.

Aquí está el fragmento de código de enlace arriba :

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);

Otros consejos

Alguien me dijo una forma aún mejor de desplazarse a una línea específica, que es fácil y funciona de maravilla.
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 Foros de MSDN :

Hice algunos cambios en la respuesta de Sam. Tenga en cuenta que quería desplazarme a la última línea. Desafortunadamente, el ListView a veces solo muestra la última línea (incluso cuando había, por ejemplo, 100 líneas encima), así que así es como lo solucioné:

    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

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

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

Gracias por el último consejo Sam. Tuve un diálogo que se abrió, lo que significa que mi cuadrícula perdió el foco 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();
}

Prueba esto

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();

}

Si solo desea 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, ScrollToEnd de ScrollViewer es en mis pruebas más confiable. En algunas pruebas, el uso del método ScrollIntoView de ListView, como el anterior, falló y no sé la razón. 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();
}

Acabo de tener el mismo problema con ItemContainerGenerator.ContainerFromItem () y ItemContainerGenerator.ContainerFromIndex () que devuelve nulo para los elementos que claramente existían en el cuadro de lista. Decasteljau tenía razón, pero tuve que investigar un poco para descubrir exactamente a qué se refería. Aquí está el desglose para ahorrarle al siguiente chico / chica algo de trabajo preliminar.

Larga historia corta, ListBoxItems se destruyen si no están a la vista. En consecuencia, ContainerFromItem () y ContainerFromIndex () devuelven un valor nulo ya que los ListBoxItems no existen. Aparentemente, esta es una característica de ahorro de memoria / rendimiento que se detalla 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 <ListBox.ItemsPanel> vacío es lo que desactiva la virtualización. Código de muestra que solucionó 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>

Para superar el problema de la virtualización pero seguir utilizando ScrollIntoView y sin piratear las entrañas de ListView, también puede usar sus objetos ViewModel para determinar qué está seleccionado.Suponiendo que tiene objetos ViewModel en su lista que presentan una IsSelected propiedad.Vincularía los elementos al ListView en XAML de esta manera:

<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>

Luego, el método de código subyacente puede desplazarse al 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 elemento seleccionado está fuera de la vista.En mi experimento, el PersonsListView.SelectedItem la propiedad era null, pero por supuesto tu ViewModel IsSelected la propiedad siempre está ahí.Asegúrese de llamar a este método después de que se hayan completado todas las vinculaciones y cargas (con el botón derecho). DispatcherPriority).

Utilizando el Ver comando patrón, su código ViewModel podría verse así:

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

En mi proyecto, necesito mostrar la línea de índice seleccionada desde la vista de lista al usuario, así que asigné el elemento seleccionado al 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);

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

Agregué el evento SelectionChanged para ListBox llamado " lbPossibleError_SelectionChanged " Agregu&eacute; el evento SelectionChanged para ListBox

luego detrás de esto " lbPossibleError_SelectionChanged " evento, aquí está el código ingrese la descripci&oacute;n de la imagen aqu&iacute;

funciona como debería para mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top