views:

1518

answers:

2

I have a class called Book;

class Book
{
 public string Name { get; set; }
 public string Author { get; set; }
 public int PagesCount { get; set; }
 public int Category { get; set; }
}

The ListBox displays a list of Books and the ItemTemplate has been modified so as to visually represent the Book. The text shows the book's Name, author and the number of pages. The category, however is represented by a certain color (for example, history is blue, romance is red etc.) Now, the text has an OuterGlowBitmap Effect and a value converter from the Category (int) to the appropriate Color. Everything is bound in DataTemplate for ListBoxItem. Technically, everything works fine.

The problem, however, is performance. It seems that the outerGlow bitmap effect is heavy on processor, so when I have a list of about 500 books, it takes about 500ms to retreive the data from the database but around 10 seconds to actually load the items into the ListBox. And even when the loading is done, scrolling is very laggy. I've tried to set the VirtualizingStackPanel.IsVirtualizing to True, but to no avail. (The maximum number of books that can be in the database at any given time is about 30000.)

However, even when there is more than 100 items in the listbox, human mind can't process that much quickly, so I don't aim to load and list to the user all the books that are searched for. That is why I have created a wrapper navigation class BookNavigator that actually binds the listbox to its ObservableCollection object. All the books are loaded into this BookNavigator, but only X of them are displayed in the listbox (by adding them to the observableCollection).

The problem with this is that I want that number of displayed books to be small enough for listbox not to display the scrollbar, so i can implement my own methods of scrolling (First, Previous, Next, Last, or simply my own scrollbar, doesn't matter).

How can I calculate how many items to display so the scrollbar is not shown?

Two problems that pop up: - Resizing the application can change the listbox's size - Not all the listbox items are of the same height (depending on the number of authors).

Is there any way to achieve what I am trying to do?


EDIT (as a reply to Martin Harris)

The problem with the code Martin Harris suggested is that the foreach loop uses FrameworkElement, but the listbox is filled with objects of type Book which does not inherit from FrameworkElement, nor it has any other mean of calculating its height. The ListBoxItem's root element is a grid, so maybe it would be possible to retreive this grid, but I don't know how to do that?

Is there any way at all to get the actual UI elements that are created to represent the listbox item?


EDIT

I found this Q/A which seems to be what I need.. ItemContainerGenerator

+2  A: 

Will you probably could figure out the sizes of all the books before adding them to the list box (possibly by parsing and populating the template XAML in code behind then asking the outer control for its size) this would be a lot of work for not much gain. Can't you just simply select a number of books which will be enough to fill the list box, but not so many to slow the rendering down and turn the scroll bar off? This could still be linked to the size of the list box so that as it grew more items were added, but the performance cost for adding a few extra items should be less that the cost of calculating all the sizes again.

Quick example: Let's say that the size of a book with one author is 150 pixels. You could take the size of the listbox and divide it by 125 to get a rough estimate of the number of items which would be in-the-ballpark, but not costly to calculate. What you want to avoid is too few items since that will leave empty space.


Edited in light of your comment.

In that case you could add the items to the list box (using the method above to get a close guess), then calculate which the last completely visible item is and then remove the extra items.

This extension method will get the last element that is completely shown in a listbox or null if no items are visible:

public static class ListBoxExtensions
{
    public static FrameworkElement GetLastItem(this ListBox listBox)
    {
        double height = listBox.ActualHeight;

        double currentHeight = 0;
        FrameworkElement previous = null;

        foreach (FrameworkElement item in listBox.Items)
        {
            currentHeight += item.ActualHeight;
            if (currentHeight > height)
            {
                return previous;
            }

            previous = item;
        }

        return previous;
    }
}

If the list is made larger then you can add enough items to the collection that you fill in the gap and re-run the process.

Martin Harris
The height of one item is from 20 to 60 pixels... So when maximized, the listbox will probably (at 1280*1024 - my res) be around 800 / which leads to about 40-50 items. This might work, but the problem is that the user can select an item and then using keyboard scroll down to those invisible items. This is unwanted scenario.
kornelijepetak
A: 

The solution can implicitly be found here: Solution

kornelijepetak