views:

1087

answers:

2

My prototype displays "documents" that contain "pages" that are represented by thumbnail images. Each document can have any number of pages. For example, there might be 1000 documents with 5 pages each, or 5 documents with 1000 pages each, or somewhere inbetween. Documents do not contain other documents. In my xaml markup I have a Listbox, who's ItemsTemplate references a innerItemsTemplate that also has a ListBox. I want the 2 levels of selected items so that I can perform varios operations on documents or pages (delete, merge, move to new location, etc). The innerItemsTemplate ListBox uses a WrapPanel as the ItemsPanelTemplate.

For the scenario where I have a large number of documents with a few pages each (say, 10000 documents with 5 pages each), the scrolling works great thanks to the UI Virtualization by the VirtualizingStackPanel. However, I have problems if I have a large number of pages. A document with 1000 pages will only display about 50 at a time (whatever fits on the screen), and when I scroll down, the outer ListBox moves to the next document, skipping the 950 pages or so that were not visible. Along with that, their is no VirtualzingWrapPanel so the app memory really increases.

I'm wondering if I am going about this the right way, especially since it is sort of difficult to explain! I would to be able to display 10000 documents with 1000 pages each (only showing whatever fits on the screen), using UI Virtualization, and also smooth scrolling.

How can I make sure the scrolling moves through all of the pages in document before it displays the next document, and still keep UI virtualization? The scrollbar seems to only move to the next document.

Does it seem logical to represent "documents" and "pages" - with my current method of using a ListBox within a ListBox?

I would very much appreciate any ideas you have. Thank You.

A: 

Please allow me to preface this answer with a question: Does the user have to see each and every thumbnail within every item in the list at all times?

If the answer to that question is 'no', then perhaps it would be feasible to limit the number of visible pages within the inner item template (given that you have indicated the scrolling works well with, say, 5 pages) and use a separate 'selected item' template that is larger and displays all pages for that document? Billy Hollis explains how to 'pop' a selected item out in a listbox on dnrtv episode 115

IanR
no the user does not have to see each and every thumbnail within every item in the list at all times - they just need to be able to scroll to get to other items
Taylor
+1  A: 

The answer here is surprising:

  • If you use ItemsControl or ListBox you will get the behavior you are experiencing, where the control scrolls "by item" so you jump over a whole document at once, BUT
  • If you use TreeView instead, the control will scroll smoothly so you can scroll through your document and into the next one, but it will still be able to virtualize.

I think the reason the WPF team chose this behavior is that TreeViewcommonly has items that are larger than the visible area, whereas typically ListBoxes don't.

In any case, it is trivial in WPF to make a TreeView look and act like a ListBox or ItemsControl by simply modifying the ItemContainerStyle. This is very straightforward. You can roll your own or just copy over the appropriate template from the system theme file.

So you will have something like this:

<TreeView ItemsSource="{Binding documents}">
  <TreeView.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel />
    </ItemsPanelTemplate>
  </TreeView.ItemsPanel>
  <TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
      <Setter Property="Template">
        <Setter.Value>
          <ControlTemplate TargetType="{x:Type TreeViewItem}">
            <ContentPresenter /> <!-- put your desired container style here  with a ContentPresenter inside -->
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </TreeView.ItemContainerStyle>
  <TreeView.ItemTemplate>
    <DataTemplate TargetType="{x:Type my:Document}">
      <Border BorderThickness="2"> <!-- your document frame will be more complicated than this -->
        <ItemsControl ItemsSource="{Binding pages}">
          ...
        </ItemsControl>
      </Border>
    </DataTemplate>
  </TreeView.ItemTemplate>
</TreeView>

Getting pixel-based scrolling and ListBox-style multiselect to work together

If you use this technique to get pixel-based scrolling, your outer ItemsControl which shows the documents cannot be a ListBox (because ListBox is not a subclass of TreeView or TreeViewItem). Thus you lose all of ListBox's multiselect support. As far as I can tell, there is no way to use these two features together without including some of your own code for one feature or the other.

If you need both sets of functionality in the same control, you have basically several options:

  1. Implement multi-selection yourself in a subclass of TreeViewItem. Use TreeViewItem instead of TreeView for the outer control, since it allows multiple children to be selected. In the template inside ItemsContainerStyle: Add a CheckBox around the ContentPresenter, template bind the CheckBox to IsSelected, and style the CheckBox with control template to get the look you want. Then add your own mouse event handlers to handle Ctrl-Click and Shift-Click for multiselect.

  2. Implement pixel-scrolled virtualization yourself in a subclass of VirtualizingPanel. This is relatively simple, since most of VirtualizingStackPanel's complexity is related to non-pixel scrolling and container recycling. Dan Crevier's Blog has some useful infromation for understanding VirtualizingPanel.

Ray Burns
Thanks, I'll give this a try, and then I will post another comment.
Taylor
This approach indeed works for me as far as UI Virtualization. Now I just need to get the ListBox like behavior for selecting items (pages or documents in this case). How can I get multiple and extended selection modes similar to ListBox?
Taylor
Also, I'm setting the ItemsPanelTemplate within the ItemsControl to a WrapPanel, which does not seem to wrap when I resize the app - it seems to behave more like a stackPanel. Overall, I feel the answer above by Ray does get me going in the right direction.
Taylor