计算ListBox中可见项目的数量

发布于 2024-07-20 09:56:11 字数 1572 浏览 5 评论 0原文

我有一个名为 Book 的课程;

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) 到适当颜色的值转换器。 一切都绑定在 ListBoxItem 的 DataTemplate 中。 从技术上讲,一切正常。

然而,问题在于性能。 看起来,outerGlow 位图效果对处理器的影响很大,因此当我有大约 500 本书的列表时,从数据库检索数据需要大约 500 毫秒,但实际将项目加载到列表框中大约需要 10 秒。 即使加载完成,滚动也非常缓慢。 我尝试将 VirtualizingStackPanel.IsVirtualizing 设置为 True,但无济于事。 (在任何给定时间数据库中可以容纳的最大书籍数量约为 30000 本书。)

但是,即使列表框中有超过 100 个项目,人脑也无法快速处理,所以我不旨在加载并向用户列出所有搜索到的书籍。 这就是为什么我创建了一个包装导航类 BookNavigator,它实际上将列表框绑定到其 ObservableCollection 对象。 所有书籍都加载到此 BookNavigator 中,但只有 X 本书显示在列表框中(通过将它们添加到 observableCollection)。

问题是我希望显示的书籍数量足够小,以便列表框不显示滚动条,这样我就可以实现自己的滚动方法(第一个、上一个、下一个、最后一个,或者只是我自己的滚动条,不没关系)。

如何计算要显示的项目数以便不显示滚动条?

弹出的两个问题: - 调整应用程序的大小可以更改列表框的大小 - 并非所有列表框项目都具有相同的高度(取决于作者的数量)。

有什么办法可以实现我想做的事情吗?


编辑(作为对 Martin Harris 的回复)

Martin Harris 建议的代码问题是 foreach 循环使用 FrameworkElement,但列表框填充了 Book 类型的对象,该对象既不继承自 FrameworkElement,也不继承它有任何其他方法来计算其高度。 ListBoxItem 的根元素是一个网格,所以也许可以检索这个网格,但我不知道该怎么做?

有什么方法可以获取为表示列表框项目而创建的实际 UI 元素吗?


编辑

我发现这个问答似乎正是我所需要的.. ItemContainerGenerator

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

策马西风 2024-07-27 09:56:11

在尝试找出类似的东西后,我想我应该在这里分享我的结果(因为它看起来比其他响应更容易):

我从 此处

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

之后,您可以循环遍历列表框项目并使用该测试来确定哪些是可见的。 该列表的计数将为您提供列表框中可见项目的数量。

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

然后,这将包含列表框中当前显示的项目列表(包括通过滚动或类似方式隐藏的项目)。

After trying to figure out something similar, I thought I would share my result here (as it seems easier than the other responses):

Simple visibility test I got from here.

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

Afterwards you can loop through the listboxitems and use that test to determine which are visible. The count of this list would give you the number of visible items in the listbox.

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

This would then contain the list of items currently shown in the listbox (including those hidden by scrolling or something similar).

水溶 2024-07-27 09:56:11

在将所有书籍添加到列表框之前,您可能可以计算出它们的大小(可能通过在后面的代码中解析和填充模板 XAML,然后询问外部控件其大小),这将是付出了很多努力却没有多少收获。 难道您不能简单地选择一些足以填满列表框的书籍,但又不会太多而导致渲染速度变慢并关闭滚动条吗? 这仍然可以与列表框的大小相关联,以便随着列表框的增长添加更多项目,但是添加一些额外项目的性能成本应该小于再次计算所有大小的成本。

简单示例:假设一本作者为一本书的大小为 150 像素。 您可以将列表框的大小除以 125,以粗略估计大致范围内的项目数量,但计算成本并不高。 你要避免的是物品太少,因为这会留下空白空间。


根据您的评论进行编辑。

在这种情况下,您可以将项目添加到列表框中(使用上面的方法进行近似猜测),然后计算最后一个完全可见的项目,然后删除多余的项目。

此扩展方法将获取列表框中完全显示的最后一个元素,如果没有可见的项目,则返回 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;
    }
}

如果列表变大,则您可以向集合中添加足够的项目来填充间隙并重新运行该过程。

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.

£冰雨忧蓝° 2024-07-27 09:56:11

“如何计算要显示的项目数,以便不显示滚动条?”

简短回答:(listBox1.Height/ 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. 设置适当大小的列表框,仅 2 行

3-7。 添加一些行

  1. 转到最后一行,只是为了好玩

  2. 在表单文本栏上显示列表框行数,基于列表框高度除以每个项目的大小。

就是这样。

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

Short answer: (listBox1.Height/ listBox1.ItemHeight)

This gives you the number of displayed/available lines, so you can read only this number of lines and will fill all the listbox.

Now, the demo:

        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();

This example let's you choose the number of items that are going to be visible, so it's the number os lines that are actualy available to show. Therefore, you add only the "listbox_total_visible_items" items and the listbox will be full and not show the scrollbars.

Code explain:

  1. listbox_total_visible_items contains the number of lines to be shown

  2. setup listbox with the proper size, 2 lines only

3-7. add some lines

  1. go to the last line, just for fun

  2. show on the form text bar, the number of the listbox lines, based on the listbox height divided by the size of each item.

That's it.

醉城メ夜风 2024-07-27 09:56:11

可以在这里隐式找到解决方案:
解决方案

The solution can implicitly be found here:
Solution

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文