Question

WPF, application similaire à un navigateur.
J'ai une page contenant un ListView. Après avoir appelé une PageFunction, j’ajoute une ligne à ListView et je souhaite faire défiler la nouvelle ligne dans l’affichage:

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

Cela fonctionne. Tant que la nouvelle ligne est visible, la ligne obtient le focus comme il se doit.

Le problème, c'est que les choses ne fonctionnent pas quand la ligne n'est pas visible.
Si la ligne n'est pas visible, il n'y a pas de ListViewItem pour la ligne générée, donc ItemContainerGenerator.ContainerFromIndex renvoie null.

Mais sans l'élément, comment faire défiler la ligne pour l'afficher? Est-il possible de faire défiler l'écran jusqu'à la dernière ligne (ou n'importe où) sans avoir besoin d'un ListViewItem?

Était-ce utile?

La solution

Je pense que le problème ici est que le ListViewItem n'est pas encore créé si la ligne n'est pas visible. WPF crée le visible sur demande.

Dans ce cas, vous obtenez probablement null pour l'élément, n'est-ce pas? (Selon votre commentaire, vous le faites)

J'ai trouvé un sur les forums MSDN qui suggèrent d’accéder directement à Scrollviewer pour pouvoir faire défiler. Pour moi, la solution présentée ressemble beaucoup à un hack, mais vous pouvez décider vous-même.

Voici l'extrait de code du lien ci-dessus :

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

Autres conseils

Quelqu'un m'a dit qu'il était encore plus efficace de faire défiler une ligne spécifique, ce qui est facile et fonctionne à merveille.
En bref:

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 version la plus longue dans Forums MSDN :

J'ai apporté quelques modifications à la réponse de Sam. Notez que je voulais faire défiler jusqu'à la dernière ligne. Malheureusement, certains éléments de la liste n’affichaient que la dernière ligne (même quand il y avait par exemple 100 lignes au-dessus), c’est pourquoi j’ai corrigé cela:

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

A bientôt

Une solution consiste à modifier le panneau ItemsPanel de ListView. Le panneau par défaut est VirtualizingStackPanel, qui crée uniquement le ListBoxItem lorsqu’il devient visible. Si vous n'avez pas trop d'éléments dans votre liste, cela ne devrait pas poser de problème.

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

Merci pour ce dernier conseil, Sam. J'avais un dialogue qui s'ouvrait, ce qui signifiait que ma grille perdait le focus à chaque fermeture du dialogue. J'utilise ceci:

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

Essayez ceci

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 vous souhaitez uniquement afficher et mettre en évidence le dernier élément après la création d'un nouvel élément de données, cette méthode est peut-être meilleure. Comparer à ScrollIntoView, ScrollToEnd of ScrollViewer est dans mes tests plus fiable. Dans certains tests utilisant la méthode ScrollIntoView de ListView comme ci-dessus a échoué et je ne sais pas la raison. Mais utiliser ScrollViewer pour aller au dernier peut fonctionner.

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

Je viens d'avoir le même problème avec ItemContainerGenerator.ContainerFromItem () et ItemContainerGenerator.ContainerFromIndex () renvoyant null pour les éléments qui existaient clairement dans la liste. Decasteljau avait raison, mais je devais faire des recherches pour comprendre exactement ce qu'il voulait dire. Voici la ventilation pour sauver le gars / gal prochain de legwork.

En résumé, les ListBoxItems sont détruits s’ils ne sont pas visibles. Par conséquent, ContainerFromItem () et ContainerFromIndex () renvoient null, car les ListBoxItems n'existent pas. Ceci est apparemment une fonctionnalité d’économie de mémoire / performance détaillée ici: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo -1-virtualizingstackpanel-vs-stackpanel-as-a-listbox-itemspanel.aspx

Le code <ListBox.ItemsPanel> vide est ce qui désactive la virtualisation. Exemple de code qui a résolu le problème pour moi:

Modèle de données:

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

Corps principal:

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

Pour résoudre le problème de la virtualisation tout en utilisant ScrollIntoView et en évitant de fouiller dans les entrailles de ListView, vous pouvez également utiliser vos objets ViewModel pour déterminer les éléments sélectionnés. En supposant que votre liste contienne des objets ViewModel dotés d'une propriété IsSelected. Vous lieriez les éléments au ListView en XAML comme ceci:

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

Ensuite, la méthode code-behind peut faire défiler jusqu'au premier élément sélectionné avec ceci:

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

Ceci fonctionne également si l'élément sélectionné est bien hors de vue. Dans mon expérience, la propriété PersonsListView.SelectedItem était null, mais bien entendu, votre propriété ViewModel DispatcherPriority est toujours présente. Assurez-vous d'appeler cette méthode une fois la liaison et le chargement terminés (avec le droit <=>).

Utilisation du modèle ViewCommand , votre code ViewModel pourrait ressembler à ceci:

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

Dans mon projet, je dois afficher la ligne d'index sélectionnée dans ListView à l'utilisateur afin d'attribuer l'élément sélectionné au contrôle ListView. Ce code fera défiler la barre de défilement et affichera l’élément sélectionné.

BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);

OU

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

ne sais pas si c'est la voie à suivre, mais cela fonctionne actuellement pour moi avec WPF, MVVM Light et .NET 3.5

J'ai ajouté l'événement SelectionChanged à ListBox appelé & "; lbPossibleError_SelectionChanged &"; J

puis derrière ce " lbPossibleError_SelectionChanged " événement, voici le code entrer la description de l

fonctionne comme il se doit pour moi.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top