Вычисление количества видимых элементов в списке

StackOverflow https://stackoverflow.com/questions/830272

  •  06-07-2019
  •  | 
  •  

Вопрос

У меня есть класс под названием "Книга";

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

В ListBox отображается список книг, а ItemTemplate был изменен таким образом, чтобы визуально представлять Книгу.В тексте указаны название книги, автор и количество страниц.Категория, однако, представлена определенным цветом (например, история - синим, романтика - красным и т.д.). Теперь текст имеет эффект OuterGlowBitmap и преобразователь значения из категории (int) в соответствующий цвет.Все привязано к DataTemplate для ListBoxItem.Технически все работает нормально.

Проблема, однако, заключается в производительности.Похоже, что эффект растрового изображения внешнего потока сильно нагружает процессор, поэтому, когда у меня есть список из примерно 500 книг, требуется около 500 мс для извлечения данных из базы данных, но около 10 секунд для фактической загрузки элементов в ListBox.И даже когда загрузка завершена, прокрутка происходит с большой задержкой.Я пытался установить VirtualizingStackPanel.IsVirtualizing в значение True, но безрезультатно.(Максимальное количество книг, которые могут находиться в базе данных в любой момент времени, составляет около 30000.)

Однако, даже когда в списке более 100 элементов, человеческий разум не может обработать столько быстро, поэтому я не ставлю перед собой цель загрузить и предоставить пользователю список всех книг, которые он ищет.Вот почему я создал класс навигации-оболочки BookNavigator, который фактически привязывает listbox к его объекту ObservableCollection.Все книги загружаются в этот BookNavigator, но только X из них отображаются в listbox (путем добавления их в ObservableCollection).

Проблема в том, что я хочу, чтобы количество отображаемых книг было достаточно маленьким, чтобы в listbox не отображалась полоса прокрутки, поэтому я могу реализовать свои собственные методы прокрутки (первая, предыдущая, Следующая, Последняя или просто моя собственная полоса прокрутки, не имеет значения).

Как я могу рассчитать, сколько элементов нужно отобразить, чтобы полоса прокрутки не отображалась?

Две всплывающие проблемы:- Изменение размера приложения может изменить размер listbox - Не все элементы listbox имеют одинаковую высоту (в зависимости от количества авторов).

Есть ли какой-нибудь способ добиться того, что я пытаюсь сделать?


РЕДАКТИРОВАТЬ (в качестве ответа Мартину Харрису)

Проблема с кодом, предложенным Мартином Харрисом, заключается в том, что цикл foreach использует FrameworkElement , но listbox заполнен объектами типа Book, которые не наследуются от FrameworkElement и не имеют никакого другого способа вычисления его высоты.Корневым элементом ListBoxItem является сетка, поэтому, возможно, было бы возможно восстановить эту сетку, но я не знаю, как это сделать?

Есть ли вообще какой-либо способ получить фактические элементы пользовательского интерфейса, которые созданы для представления элемента listbox?


Редактировать

Я нашел этот вопрос-ответ, который, кажется, то, что мне нужно..Генератор ItemContainerGenerator

Это было полезно?

Решение 4

Решение неявно может быть найдено здесь:Решение

Другие советы

Попытавшись придумать что-то подобное, я подумал, что поделюсь своим результатом здесь (поскольку это кажется проще, чем другие ответы).:

Простой тест на наглядность, который я получил от здесь.

private static bool IsUserVisible(FrameworkElement element, FrameworkElement container)
{
    if (!element.IsVisible)
        return false;

    Rect bounds =
        element.TransformToAncestor(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
    var rect = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
    return rect.Contains(bounds.TopLeft) || rect.Contains(bounds.BottomRight);
}

После этого вы можете перебрать элементы listboxitems и использовать этот тест, чтобы определить, какие из них видны.Количество этого списка даст вам количество видимых элементов в списке.

private List<object> GetVisibleItemsFromListbox(ListBox listBox, FrameworkElement parentToTestVisibility)
{
    var items = new List<object>();

    foreach (var item in PhotosListBox.Items)
    {
        if (IsUserVisible((ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item), parentToTestVisibility))
        {
            items.Add(item);
        }
        else if (items.Any())
        {
            break;
        }
    }

    return items;
}

Затем это будет содержать список элементов, которые в данный момент отображаются в listbox (включая те, которые скрыты прокруткой или чем-то подобным).

Будете ли вы, вероятно, мог бы выясните размеры всех книг, прежде чем добавлять их в список (возможно, путем синтаксического анализа и заполнения шаблона XAML в коде позади, а затем запрашивая внешний элемент управления о его размере), это было бы большой работой без особого выигрыша.Разве вы не можете просто выбрать количество книг, которого будет достаточно, чтобы заполнить поле списка, но не так много, чтобы замедлить рендеринг и отключить полосу прокрутки?Это все еще может быть связано с размером поля списка, чтобы по мере его увеличения добавлялось больше элементов, но затраты на производительность при добавлении нескольких дополнительных элементов должны быть меньше затрат на повторное вычисление всех размеров.

Краткий пример:Допустим, размер книги с одним автором составляет 150 пикселей.Вы могли бы взять размер listbox и разделить его на 125, чтобы получить приблизительную оценку количества элементов, которое было бы приемлемым, но не дорогостоящим для вычисления.Чего вы хотите избежать, так это слишком малого количества элементов, так как это оставит пустое пространство.


Отредактировано в свете вашего комментария.

В этом случае вы могли бы добавить элементы в поле списка (используя описанный выше метод, чтобы получить приблизительное представление), затем вычислить, какой из них является последним полностью видимым элементом, а затем удалить лишние элементы.

Этот метод расширения получит последний элемент, который полностью отображается в listbox или null, если элементы не видны:

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

Если список будет увеличен, вы сможете добавить в коллекцию достаточное количество элементов, чтобы заполнить пробел, и повторно запустить процесс.

"Как я могу рассчитать, сколько элементов нужно отобразить, чтобы полоса прокрутки не отображалась?"

Краткий ответ:(Список 1.Высота/ listBox1.ItemHeight)

Это дает вам количество отображаемых / доступных строк, поэтому вы можете прочитать только это количество строк и заполните все поле списка.

Итак, демо-версия:

        int listbox_total_visible_lines = 2;
        listBox1.Height = (listbox_total_visible_lines + 1) * listBox1.ItemHeight;
        listBox1.Items.Add("um");
        listBox1.Items.Add("dois");
        listBox1.Items.Add("tres");
        listBox1.Items.Add("quatro");
        listBox1.Items.Add("cinco");
        listBox1.SelectedIndex = listBox1.Items.Count - 1;
        this.Text = (listBox1.Height/ listBox1.ItemHeight).ToString();

В этом примере давайте выберем количество элементов, которые будут видны, то есть количество строк, которые действительно доступны для отображения.Следовательно, вы добавляете только элементы "listbox_total_visible_items", и поле списка будет заполнено и не будет отображать полосы прокрутки.

Объяснение кода:

  1. listbox_total_visible_items содержит количество строк, которые должны быть показаны

  2. установите listbox нужного размера, всего 2 строки

3-7.добавьте несколько строк

  1. переходите к последней строчке, просто ради удовольствия

  2. покажите в текстовой строке формы количество строк списка на основе высоты списка, деленной на размер каждого элемента.

Вот и все.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top