Pergunta

Eu tenho uma visão de árvore que está ligada a uma árvore de instâncias de viewmodel. O problema é que os dados do modelo são provenientes de um repositório lento, por isso preciso de virtualização de dados. A lista de Sub ViewModel abaixo de um nó deve ser carregada apenas quando o nó de visualização da árvore pai for expandido e deve ser descarregado quando for desabar.

Como isso pode ser implementado ao aderir aos princípios do MVVM? Como o ViewModel pode ser notificado de que precisa carregar ou descarregar subnodos? Foi quando um nó foi expandido ou desabar sem saber nada sobre a existência do TreeView?

Algo me faz sentir que a virtualização de dados não combina bem com o MVVM. Como na virtualização de dados, o ViewModel geralmente precisa saber bastante sobre o estado atual da interface do usuário e o ASLO precisa controlar muitos aspectos na interface do usuário. Tome outro exemplo:

Um listView com virtualização de dados. O ViewModel precisaria controlar o comprimento do scrollthumbumb do ListView, pois depende do número de itens no modelo. Além disso, quando o usuário rola, o ViewModel precisaria saber para qual posição ele rolou e quão grande é o ListView (quantos itens atualmente se encaixam) para poder carregar a parte certa dos dados do modelo formam o repositório.

Foi útil?

Solução

A maneira mais fácil de resolver isso é com uma implementação de "coleção virtualizante" que mantém referências fracas aos seus itens, juntamente com um algoritmo para buscar / criar itens. O código para esta coleção é bastante complexo, com todas as interfaces necessárias e as estruturas de dados para rastrear com eficiência faixas de dados carregados, mas aqui está uma API parcial para uma classe que virtualizou com base nos índices:

public class VirtualizingCollection<T>
  : IList<T>, ICollection<T>, IEnumerable<T>,
    IList, ICollection, IEnumerable,
    INotifyPropertyChanged, INotifyCollectionChanged
{
  protected abstract void FetchItems(int requestedIndex, int gapStartIndex, int gapEndIndex);
  protected void RecordFetchedItems(int startIndex, int count, IEnumerable items) ...
  protected void RecordInsertOrDelete(int startIndex, int countPlusOrMinus) ...
  protected virtual void OnCollectionChanged(CollectionChangedEventArgs e) ...
  protected virtual void Cleanup();
}

A estrutura de dados interna aqui é uma árvore equilibrada de faixas de dados, com cada faixa de dados contendo um índice de partida e uma matriz de referências fracas.

Esta classe foi projetada para ser subclassificada para fornecer a lógica para realmente carregar os dados. Aqui está como funciona:

  • No construtor subclasse, RecordInsertOrDelete é chamado para definir o tamanho da coleção inicial
  • Quando um item é acessado usando IList/ICollection/IEnumerable, a árvore é usada para encontrar o item de dados. Se encontrado na árvore e há uma referência fraca e a referência fraca ainda aponta para um objeto de vida, esse objeto é retornado, caso contrário, ele é carregado e devolvido.
  • Quando um item precisa ser carregado, um intervalo de índice é calculado pesquisando para frente e para trás, embora a estrutura de dados do próximo/anterior já carregado, então o resumo FetchItems é chamado para que a subclasse possa carregar os itens.
  • Na subclasse FetchItems implementação, itens são buscados e depois RecordFetchedItems é chamado para atualizar a árvore dos intervalos com os novos itens. Alguma complexidade aqui é necessária para mesclar nós adjacentes para evitar muito crescimento de árvores.
  • Quando a subclasse recebe uma notificação de alterações de dados externas, ele pode chamar RecordInsertOrDelete Para atualizar o rastreamento do índice. Isso atualiza os índices iniciais. Para uma inserção, isso também pode dividir um intervalo e, para uma exclusão, isso pode exigir que uma ou mais faixas sejam recriadas menores. Esse mesmo algoritmo é usado internamente quando os itens são adicionados / excluídos através do IList e IList<T> interfaces.
  • o Cleanup o método é chamado em segundo plano para pesquisar incrementalmente a árvore de intervalos para WeakReferences e intervalos inteiros que podem ser descartados, e também para intervalos que são muito escassos (por exemplo, apenas um WeakReference em uma faixa com 1000 slots)

Observe que FetchItems é passado uma variedade de itens descarregados para que possa usar uma heurística para carregar vários itens de uma só vez. Uma heurística simples, assim, estaria carregando os próximos 100 itens ou até o final da lacuna atual, o que ocorrer primeiro.

Com um VirtualizingCollection, A virtualização interna do WPF causará carregamento de dados nos momentos apropriados para ListBox, ComboBox, etc, desde que você esteja usando, por exemplo. VirtualizingStackPanel ao invés de StackPanel.

Para TreeView, mais um passo é necessário: no HierarchicalDataTemplate defina a MultiBinding por ItemsSource que se liga ao seu real ItemsSource e também para IsExpanded no pai modelo. O conversor para o MultiBinding retorna seu primeiro valor (o ItemsSource) se o segundo valor (o IsExpanded valor) é verdadeiro, caso contrário, retorna nulo. O que isso faz é fazer com que quando você colapse um nó no TreeView Todas as referências ao conteúdo da coleção são imediatamente descartadas para que VirtualizingCollection pode limpá -los.

Observe que a virtualização não precisa ser feita com base nos índices. Em um cenário de árvore, pode ser tudo ou nada, e em um cenário de lista, uma contagem estimada pode ser usada e as faixas preenchidas conforme necessário usando um mecanismo de "tecla final" / "chave final". Isso é útil quando os dados subjacentes podem alterar e a visualização virtualizada deve rastrear seu local atual com base em qual chave está na parte superior da tela.

Outras dicas

    <TreeView
        VirtualizingStackPanel.IsVirtualizing = "True"
        VirtualizingStackPanel.VirtualizationMode = "Recycling" 
VirtualizingStackPanel.CleanUpVirtualizedItem="TreeView_CleanUpVirtualizedItem">
            <TreeView.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel />
                </ItemsPanelTemplate>
            </TreeView.ItemsPanel>
        </TreeView>
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top