WPF ListBox com um ListBox – virtualização de interface do usuário e rolagem

Meu protótipo exibe “documentos” que contêm “páginas” representadas por imagens em miniatura. Cada documento pode ter qualquer número de páginas. Por exemplo, pode haver 1000 documentos com 5 páginas cada um, ou 5 documentos com 1000 páginas cada um, ou algum lugar entre eles. Documentos não contêm outros documentos. Na minha marcação xaml eu tenho um ListBox , cujo ItemsTemplate referência a um innerItemsTemplate que também tem um ListBox . Eu quero os 2 níveis de itens selecionados para que eu possa executar várias operações em documentos ou páginas (excluir, mesclar, mover para novo local, etc). O innerItemsTemplate ListBox usa um WrapPanel como o ItemsPanelTemplate .

Para o cenário em que tenho um grande número de documentos com algumas páginas cada (digamos, 10000 documentos com 5 páginas cada), a rolagem funciona muito bem graças à Virtualização da interface do usuário pelo VirtualizingStackPanel . No entanto, tenho problemas se tiver um grande número de páginas. Um documento com 1000 páginas exibirá apenas cerca de 50 por vez (o que couber na canvas) e, quando eu rolar para baixo, o ListBox externo se move para o próximo documento, ignorando as 950 páginas que não estavam visíveis. Junto com isso, não há VirtualzingWrapPanel então a memory do aplicativo realmente aumenta.

Eu estou querendo saber se eu estou indo sobre isso da maneira certa, especialmente porque é um pouco difícil de explicar! Eu gostaria de ser capaz de exibir 10000 documentos com 1000 páginas cada (mostrando apenas o que se encheckbox na canvas), usando a virtualização de interface do usuário e também a rolagem suave.

Como posso ter certeza de que a rolagem percorre todas as páginas do documento antes de exibir o próximo documento e ainda manter a virtualização da interface do usuário? A barra de rolagem parece apenas passar para o próximo documento.

Parece lógico representar “documentos” e “páginas” – com o meu método atual de usar um ListBox em um ListBox ?

Eu apreciaria muito todas as idéias que você tem. Obrigado.

A resposta aqui é surpreendente:

  • Se você usar ItemsControl ou ListBox você obterá o comportamento que está ocorrendo, onde o controle rola “por item” para que você salte sobre um documento inteiro de uma só vez, MAS
  • Se você usar TreeView , o controle rolará suavemente para que você possa rolar pelo documento até o próximo, mas ele ainda poderá virtualizar.

Eu acho que a razão pela qual a equipe do WPF escolheu esse comportamento é que o TreeView normalmente tem itens que são maiores que a área visível, enquanto que tipicamente o ListBox não tem.

Em qualquer caso, é trivial no WPF fazer uma aparência de TreeView e agir como um ListBox ou ItemsControl simplesmente modificando o ItemContainerStyle . Isso é muito simples. Você pode rolar seu próprio ou apenas copiar o modelo apropriado do arquivo de tema do sistema.

Então você terá algo assim:

               ...      

Obtendo rolagem baseada em pixel e multiselect no estilo ListBox para trabalhar juntos

Se você usar essa técnica para obter rolagem baseada em pixel, o seu ItemsControl externo que mostra os documentos não pode ser um ListBox (porque ListBox não é uma subclass de TreeView ou TreeViewItem). Assim, você perde todo o suporte multiselect do ListBox. Tanto quanto eu posso dizer, não há maneira de usar esses dois resources juntos sem include alguns de seu próprio código para um recurso ou outro.

Se você precisar dos dois conjuntos de funcionalidades no mesmo controle, basicamente terá várias opções:

  1. Implemente a seleção múltipla em uma subclass de TreeViewItem. Use TreeViewItem em vez de TreeView para o controle externo, pois permite que vários filhos sejam selecionados. No modelo dentro de ItemsContainerStyle: Adicione um CheckBox ao redor do ContentPresenter, molde vincule o CheckBox ao IsSelected e estilize o CheckBox com o template de controle para obter a aparência desejada. Em seguida, adicione seus próprios manipuladores de events de mouse para manipular Ctrl-Click e Shift-Click para multiselecionar.

  2. Implemente a virtualização com rolagem de pixels em uma subclass do VirtualizingPanel. Isso é relativamente simples, já que a maior parte da complexidade do VirtualizingStackPanel está relacionada à rolagem sem pixels e à recyclerview de contêineres. O Blog de Dan Crevier tem algumas informações úteis para entender o VirtualizingPanel.

É possível obter VirtualizingStackPanels de rolagem suave no WPF 4.0 sem sacrificar a virtualização se você estiver preparado para usar a reflection para acessar a funcionalidade privada do VirtualizingStackPanel. Tudo o que você precisa fazer é definir a propriedade IsPixelBased privada do VirtualizingStackPanel como true.

Note que no .net 4.5 não há necessidade para este hack como você pode definir VirtualizingPanel.ScrollUnit = “Pixel”.

Para facilitar, aqui está um código:

 public static class PixelBasedScrollingBehavior { public static bool GetIsEnabled(DependencyObject obj) { return (bool)obj.GetValue(IsEnabledProperty); } public static void SetIsEnabled(DependencyObject obj, bool value) { obj.SetValue(IsEnabledProperty, value); } public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(PixelBasedScrollingBehavior), new UIPropertyMetadata(false, HandleIsEnabledChanged)); private static void HandleIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var vsp = d as VirtualizingStackPanel; if (vsp == null) { return; } var property = typeof(VirtualizingStackPanel).GetProperty("IsPixelBased", BindingFlags.NonPublic | BindingFlags.Instance); if (property == null) { throw new InvalidOperationException("Pixel-based scrolling behaviour hack no longer works!"); } if ((bool)e.NewValue == true) { property.SetValue(vsp, true, new object[0]); } else { property.SetValue(vsp, false, new object[0]); } } } 

Para usar isso em um ListBox, por exemplo, você faria:

         

O .NET 4.5 agora tem a propriedade VirtualizingPanel.ScrollUnit="ScrollUnit" . Acabei de converter um dos meus TreeViews em um ListBox e o desempenho foi notavelmente melhor.

Mais informações aqui: http://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.scrollunit(v=vs.110).aspx

Isso funcionou para mim. Parece que alguns atributos simples irão fazê-lo (.NET 4.5)

  

Por favor, permita-me apresentar esta resposta com uma pergunta: O usuário tem que ver cada miniatura em cada item da lista em todos os momentos?

Se a resposta a essa pergunta for “não”, talvez seja viável limitar o número de páginas visíveis dentro do modelo de item interno (dado que você indicou que a rolagem funciona bem com, digamos, 5 páginas) e usar um arquivo separado modelo de ‘item selecionado’ que é maior e exibe todas as páginas para esse documento? Billy Hollis explica como ‘pop’ um item selecionado em uma checkbox de listview no episódio dnrtv 115