Domanda

WPF, app Browserlike.
Ho una pagina contenente un ListView. Dopo aver chiamato un PageFunction aggiungo una linea a ListView e voglio scorrere la nuova linea in vista:

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

Funziona. Finché la nuova linea è in vista, la linea ottiene lo stato attivo come dovrebbe.

Il problema è che le cose non funzionano quando la linea non è visibile.
Se la riga non è visibile, non esiste alcun ListViewItem per la riga generata, quindi ItemContainerGenerator.ContainerFromIndex restituisce null.

Ma senza l'elemento, come faccio a scorrere la linea in vista? C'è un modo per scorrere fino all'ultima riga (o ovunque) senza bisogno di un ListViewItem?

È stato utile?

Soluzione

Penso che il problema qui sia che ListViewItem non è stato ancora creato se la linea non è visibile. WPF crea il Visible on demand.

Quindi in questo caso probabilmente ottieni null per l'articolo, vero? (Secondo il tuo commento, lo fai)

Ho trovato a link su forum MSDN che suggeriscono di accedere direttamente a Scrollviewer per poter scorrere. Per me la soluzione presentata sembra molto simile a un hack, ma puoi decidere tu stesso.

Ecco lo snippet di codice da link sopra :

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

Altri suggerimenti

Qualcuno mi ha detto un modo ancora migliore di scorrere fino a una linea specifica, che è facile e funziona come un fascino.
In breve:

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 versione più lunga in Forum MSDN :

Ho apportato alcune modifiche alla risposta di Sam. Nota che volevo scorrere fino all'ultima riga. Sfortunatamente ListView a volte ha appena visualizzato l'ultima riga (anche quando c'erano ad esempio 100 righe sopra di essa), quindi è così che l'ho risolto:

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

Saluti

Una soluzione a questo è cambiare il ItemsPanel di ListView. Il pannello predefinito è VirtualizingStackPanel che crea ListBoxItem solo la prima volta che diventano visibili. Se non hai troppi elementi nell'elenco, non dovrebbe essere un problema.

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

Grazie per l'ultimo suggerimento Sam. Ho aperto una finestra di dialogo, il che significa che la mia griglia ha perso la messa a fuoco ogni volta che la finestra di dialogo si è chiusa. Uso questo:

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

Prova questo

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

}

Se vuoi solo mostrare e focalizzare l'ultimo elemento dopo aver creato un nuovo elemento di dati, questo metodo è forse migliore. Confronta con ScrollIntoView, ScrollToEnd di ScrollViewer è nei miei test più affidabile. In alcuni test utilizzando il metodo ScrollIntoView di ListView come sopra fallito e non conosco il motivo. Ma usare ScrollViewer per scorrere fino all'ultimo può funzionare.

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

Ho avuto lo stesso problema con ItemContainerGenerator.ContainerFromItem () e ItemContainerGenerator.ContainerFromIndex () restituendo null per gli elementi chiaramente presenti nella casella di riepilogo. Decasteljau aveva ragione, ma ho dovuto scavare per capire esattamente cosa intendesse dire. Ecco la ripartizione per salvare il prossimo ragazzo / ragazza un po 'di legwork.

Per farla breve, ListBoxItems vengono distrutti se non sono in vista. Di conseguenza ContainerFromItem () e ContainerFromIndex () restituiscono null poiché ListBoxItems non esiste. Questa è apparentemente una funzione di risparmio di memoria / prestazioni qui dettagliata: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo -1-VirtualizingStackPanel-vs-StackPanel-as-a-casella di riepilogo-itemspanel.aspx

Il codice <ListBox.ItemsPanel> vuoto è ciò che disattiva la virtualizzazione. Codice di esempio che ha risolto il problema per me:

Modello di dati:

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

Corpo principale:

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

Per superare il problema di virtualizzazione ma utilizzare ancora ScrollIntoView e non hackerare le viscere di ListView, è possibile utilizzare anche gli oggetti ViewModel per determinare ciò che è selezionato. Supponendo che nel proprio elenco siano presenti oggetti ViewModel che presentano una proprietà IsSelected. Collegheresti gli elementi a ListView in XAML in questo modo:

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

Quindi, il metodo code-behind può scorrere fino al primo elemento selezionato con questo:

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

Funziona anche se l'elemento selezionato è ben nascosto. Nel mio esperimento, la proprietà PersonsListView.SelectedItem era null, ma ovviamente la proprietà ViewModel DispatcherPriority è sempre presente. Assicurati di chiamare questo metodo dopo aver completato tutti i collegamenti e il caricamento (con il diritto <=>).

Uso del ViewCommand , il tuo codice ViewModel potrebbe apparire così:

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

Nel mio progetto ho bisogno di mostrare all'utente la linea di indice selezionata dalla visualizzazione elenco, quindi ho assegnato l'elemento selezionato al controllo ListView. Questo codice farà scorrere la barra di scorrimento e visualizzerà l'elemento selezionato.

BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);

o

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

non sono sicuro che questa sia la strada da percorrere, ma attualmente funziona per me usando WPF, MVVM Light e .NET 3.5

Ho aggiunto l'evento SelectionChanged per ListBox chiamato " lbPossibleError_SelectionChanged " Ho aggiunto l

quindi dietro questa " lbPossibleError_SelectionChanged " evento, ecco il codice inserisci qui la descrizione dell

funziona come dovrebbe per me.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top