Pergunta

WPF, Browserlike aplicativo.
Eu tenho uma página que contém um ListView. Depois de chamar um PageFunction adicionar uma linha para o ListView e deseja rolar a nova linha à vista:

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

Isso funciona. Enquanto a nova linha está em vista a linha recebe o foco como deveria.

O problema é que as coisas não funcionam quando a linha não é visível.
Se a linha não é visível, não há ListViewItem para a linha gerada, de modo ItemContainerGenerator.ContainerFromIndex retorna null.

Mas sem o item, como faço para rolar a linha em vista? Existe alguma maneira para ir até a última linha (ou em qualquer lugar) sem precisar de um ListViewItem?

Foi útil?

Solução

Eu acho que o problema aqui é que o ListViewItem não é criado ainda se a linha não é visível. WPF cria a Visible sob demanda.

Portanto, neste caso, você provavelmente terá null para o item, não é? (De acordo com o seu comentário, você faz)

Eu encontrei um link em fóruns do MSDN que sugerem acessando o ScrollViewer diretamente , a fim de rolagem. Para mim, a solução apresentada não se parece muito com um hack, mas você pode decidir por si mesmo.

Aqui está o trecho de código a partir do link acima :

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

Outras dicas

Alguém me disse uma maneira ainda melhor para se deslocar para uma linha específica, que é fácil e funciona como charme.
Em suma:

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

A versão mais longa em Fórum MSDN :

Eu fiz algumas mudanças para a resposta de Sam. Note que eu queria para ir até a última linha. Infelizmente, a ListView sometiems apenas exibida a última linha (mesmo quando havia por exemplo 100 linhas acima dela), então isso é como eu fixo 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();
        }
    }

Felicidades

Uma solução para isso é mudar os ItemsPanel do ListView. O painel padrão é o VirtualizingStackPanel que só cria a ListBoxItem a primeira vez que eles se tornam visíveis. Se você não tem muitos itens em sua lista, ele não deve ser um problema.

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

Obrigado por essa última ponta Sam. Eu tinha uma janela que abriu, o que significa minha grade perdeu o foco cada vez que o diálogo fechado. Eu uso este:

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

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

}

Se você quiser apenas para mostrar e focar o último item após criar um novo item de dados, este método é talvez melhor. Compare com ScrollIntoView, ScrollToEnd de ScrollViewer está em meus testes mais confiável. Em alguns testes, usando o método ScrollIntoView de ListView como acima falhou e eu não sei a razão. Mas usando ScrollViewer para se deslocar para último pode 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();
}

Eu apenas tive o mesmo problema com ItemContainerGenerator.ContainerFromItem () e ItemContainerGenerator.ContainerFromIndex () retornando nulo para itens que claramente existiam na caixa de listagem. Decasteljau estava certo, mas eu tinha que fazer alguma escavação para descobrir exatamente o que ele queria dizer. Aqui é a quebra para salvar o próximo cara / gal algum trabalho braçal.

Para encurtar a história, ListBoxItems são destruídos se não forem dentro da visão. Consequentemente ContainerFromItem () e ContainerFromIndex () return null desde que não existem as ListBoxItems. Este é, aparentemente, um recurso de economia de memória / desempenho detalhado aqui: http://blogs.msdn.com/b/oren/archive/2010/11/08/wp7-silverlight-perf-demo -1-VirtualizingStackPanel-vs-StackPanel-como-um-listagem-itemspanel.aspx

O código <ListBox.ItemsPanel> vazio é o que desliga a virtualização. código de exemplo que corrigiu o problema para mim:

modelo de dados:

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

Corpo 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 o problema de virtualização, mas ainda usar ScrollIntoView e não de hackers ao redor nas entranhas do ListView, você também pode usar o seu ViewModel objetos para determinar o que está selecionado. Supondo que você tenha objetos ViewModel em sua lista que apresentam uma propriedade IsSelected. Você iria ligar os itens para o ListView em XAML como este:

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

Em seguida, o código-behind método pode se deslocar para o primeiro item selecionado com esta:

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

Isso também funciona se o item selecionado é bem fora de vista. Na minha experiência, a propriedade PersonsListView.SelectedItem foi null, mas é claro que sua propriedade IsSelected ViewModel está sempre lá. Certifique-se de chamar esse método, afinal de ligação e carregamento foi concluído (com a DispatcherPriority direita).

Usando o padrão ViewCommand , seu código ViewModel poderia ser assim:

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

No meu projeto eu preciso para exibir a linha de índice selecionado a partir do listview para o usuário assim que eu atribuído o item selecionado para o controle ListView. Este código irá rolar a barra de rolagem e exibir o item selecionado.

BooleanListView.ScrollIntoView (BooleanListView.SelectedItem);

ou

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

não tenho certeza se este é o caminho a percorrer, mas esta atualmente trabalha para mim usando WPF, MVVM Luz, e .NET 3.5

Eu adicionei o evento SelectionChanged para ListBox chamado " lbPossibleError_SelectionChanged " eu adicionei o evento SelectionChanged para ListBox

, em seguida, por trás disso " lbPossibleError_SelectionChanged " evento, aqui está o código enter descri&ccedil;&atilde;o da imagem aqui

funciona como deveria para mim.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top