WPF ListBox VirtualizingPanel.CacheLengthUnit="Item" VirtualizingPanel.CacheLength="5,5"

Install-Package Microsoft.Extensions.DependencyInjection;

 

Totally speaking, VirtualizationPanel.CacheLengthUnit's value is item and its performance is better than pixel, the latter may lead to 

  • Unpredictable memory usage - Caches as many items as fit in the pixel range

  • Performance issues with variable-height items

  • Can cache too many items if items are small, wasting memory

  • Harder to optimize since item count varies based on content

 

VirtualizingPanel.CacheLengthUnit="Item"
VirtualizingPanel.CacheLength="5,5"

 

CacheLength="Before,After"

First number (5) = Items cached BEFORE the visible viewport

Second number (5) = Items cached AFTER the visible viewport

Total cached items = 5 + 5 = 10 items (plus whatever is visible)

 

 

<ListBox ItemsSource="{Binding BooksCollection}"
             VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.VirtualizationMode="Recycling"
             VirtualizingPanel.CacheLengthUnit="Item"
             VirtualizingPanel.CacheLength="5,5"
             ScrollViewer.IsDeferredScrollingEnabled="True"
             ScrollViewer.CanContentScroll="True">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.Resources>
                    <Style TargetType="TextBlock">
                        <Setter Property="FontSize" Value="30"/>
                        <Setter Property="Width" Value="Auto"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="FontSize" Value="50"/>
                                <Setter Property="Foreground" Value="Red"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Grid.Resources>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
                <TextBlock Text="{Binding Id}" Grid.Column="0"/>
                <TextBlock Text="{Binding Name}" Grid.Column="1"/>
                <TextBlock Text="{Binding Author}" Grid.Column="2"/>
                <TextBlock Text="{Binding ISBN}" Grid.Column="3"/>
                <TextBlock Text="{Binding Title}" Grid.Column="4"/>
                <TextBlock Text="{Binding Topic}" Grid.Column="5"/>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

public async Task InitBooksCollectionAsync()
{
    StatusMsg = $"Loading...,{GetMemory()},{GetTimeCost()}";
    BooksCollection = new ObservableCollection<Book>();
    List<Book> booksList = new List<Book>();

    for (int i = 1; i < 100000001; i++)
    {
        booksList.Add(new Book()
        {
            Id = idService.GetID(),
            Author = $"Author_{i}",
            Name = nameService.GetName(),
            ISBN = isbnService.GetISBN(),
            Title = $"Title_{i}",
            Topic = $"Topic_{i}"
        });

        if (i < 1001 && i % 100 == 0)
        {
            await PopulateBooksCollectionAsync(booksList);
        }
        else if (i > 1000 && i % 1000000 == 0)
        {
            await PopulateBooksCollectionAsync(booksList);
        }
    }

    if (booksList.Any())
    {
        await PopulateBooksCollectionAsync(booksList);
    }

    StatusMsg = $"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";
    MessageBox.Show(StatusMsg);
}

private async Task PopulateBooksCollectionAsync(List<Book> booksList)
{
    List<Book> tempList = new List<Book>();
    lock (objLock)
    {
        tempList = booksList.ToList();
        booksList.Clear();
    }
    await Application.Current.Dispatcher.InvokeAsync(() =>
    {
        foreach (var book in tempList)
        {
            BooksCollection.Add(book);
        }
        StatusMsg = $"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";
    }, DispatcherPriority.Background);
}

private string GetMemory()
{
    var procMemory = Process.GetCurrentProcess().PrivateMemorySize64 / 1024.0d / 1024.0d;
    return $"Memory:{procMemory.ToString("#,##0.00")} M";
}

private string GetTimeCost()
{
    return $"Time cost:{watch.Elapsed.TotalSeconds.ToString("#,##0.00")} seconds!";
}

 

 

 

 

image

 

 

image

 

//App.xaml
<Application x:Class="WpfApp6.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp6">
    <Application.Resources>
         
    </Application.Resources>
</Application>


//App.xaml.cs
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
using System.Data;
using System.Windows;

namespace WpfApp6
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        ServiceProvider serviceProvider;

        public App()
        {
            var services=new ServiceCollection();
            ConfigureServices(services);

            serviceProvider = services.BuildServiceProvider();
            var mainWin=serviceProvider.GetRequiredService<MainWindow>();
            mainWin?.Show();
        }

        private void ConfigureServices(ServiceCollection services)
        {
            services.AddSingleton<IIDService, IDService>();
            services.AddSingleton<INameService, NameService>();
            services.AddSingleton<IISBNService,ISBNService>();
            services.AddSingleton<MainWindow>();
            services.AddSingleton<MainVM>();
        }

        protected override void OnExit(ExitEventArgs e)
        {
            serviceProvider?.Dispose();
            base.OnExit(e);
        }
    }

}



//MainWindow.xaml
<Window x:Class="WpfApp6.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp6"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="{Binding StatusMsg}" Height="450" Width="800">
    <Grid>
        <ListBox ItemsSource="{Binding BooksCollection}"
                     VirtualizingPanel.IsVirtualizing="True"
                     VirtualizingPanel.VirtualizationMode="Recycling"
                     VirtualizingPanel.CacheLengthUnit="Item"
                     VirtualizingPanel.CacheLength="5,5"
                     ScrollViewer.IsDeferredScrollingEnabled="True"
                     ScrollViewer.CanContentScroll="True">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.Resources>
                            <Style TargetType="TextBlock">
                                <Setter Property="FontSize" Value="30"/>
                                <Setter Property="Width" Value="Auto"/>
                                <Style.Triggers>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="FontSize" Value="50"/>
                                        <Setter Property="Foreground" Value="Red"/>
                                    </Trigger>
                                </Style.Triggers>
                            </Style>
                        </Grid.Resources>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                            <ColumnDefinition/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text="{Binding Id}" Grid.Column="0"/>
                        <TextBlock Text="{Binding Name}" Grid.Column="1"/>
                        <TextBlock Text="{Binding Author}" Grid.Column="2"/>
                        <TextBlock Text="{Binding ISBN}" Grid.Column="3"/>
                        <TextBlock Text="{Binding Title}" Grid.Column="4"/>
                        <TextBlock Text="{Binding Topic}" Grid.Column="5"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>



//MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using System.Windows.Threading;

namespace WpfApp6
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        public MainWindow(MainVM vm)
        {
            InitializeComponent();
            RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.Default;
            this.DataContext = vm;
            this.Loaded += async (s, e) =>
            {
                await vm.InitBooksCollectionAsync();
            };
        }
    }

    public class MainVM : INotifyPropertyChanged
    {
        private IIDService idService;
        private INameService nameService;
        private IISBNService isbnService;
        private object objLock = new object();
        private Stopwatch watch;
        public MainVM(IIDService idServiceValue, INameService nameServiceValue, IISBNService isbnServiceValue)
        {
            idService = idServiceValue;
            nameService = nameServiceValue;
            isbnService = isbnServiceValue;
            watch = new Stopwatch();
            watch.Start();
        }

        public async Task InitBooksCollectionAsync()
        {
            StatusMsg = $"Loading...,{GetMemory()},{GetTimeCost()}";
            BooksCollection = new ObservableCollection<Book>();
            List<Book> booksList = new List<Book>();

            for (int i = 1; i < 100000001; i++)
            {
                booksList.Add(new Book()
                {
                    Id = idService.GetID(),
                    Author = $"Author_{i}",
                    Name = nameService.GetName(),
                    ISBN = isbnService.GetISBN(),
                    Title = $"Title_{i}",
                    Topic = $"Topic_{i}"
                });

                if (i < 1001 && i % 100 == 0)
                {
                    await PopulateBooksCollectionAsync(booksList);
                }
                else if (i > 1000 && i % 1000000 == 0)
                {
                    await PopulateBooksCollectionAsync(booksList);
                }
            }

            if (booksList.Any())
            {
                await PopulateBooksCollectionAsync(booksList);
            }

            StatusMsg = $"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";
            MessageBox.Show(StatusMsg);
        }

        private async Task PopulateBooksCollectionAsync(List<Book> booksList)
        {
            List<Book> tempList = new List<Book>();
            lock (objLock)
            {
                tempList = booksList.ToList();
                booksList.Clear();
            }
            await Application.Current.Dispatcher.InvokeAsync(() =>
            {
                foreach (var book in tempList)
                {
                    BooksCollection.Add(book);
                }
                StatusMsg = $"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";
            }, DispatcherPriority.Background);
        }

        private string GetMemory()
        {
            var procMemory = Process.GetCurrentProcess().PrivateMemorySize64 / 1024.0d / 1024.0d;
            return $"Memory:{procMemory.ToString("#,##0.00")} M";
        }

        private string GetTimeCost()
        {
            return $"Time cost:{watch.Elapsed.TotalSeconds.ToString("#,##0.00")} seconds!";
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName = "")
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                handler?.Invoke(this, new PropertyChangedEventArgs(propName));
            }
        }

        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                if (value != booksCollection)
                {
                    booksCollection = value;
                    OnPropertyChanged();
                }
            }
        }

        private string statusMsg;
        public string StatusMsg
        {
            get
            {
                return statusMsg;
            }
            set
            {
                if (value != statusMsg)
                {
                    statusMsg = value;
                    OnPropertyChanged();
                }
            }
        }
    }

    public interface IIDService
    {
        int GetID();
    }

    public class IDService : IIDService
    {
        int id = 0;

        public int GetID()
        {
            return Interlocked.Increment(ref id);
        }
    }

    public interface INameService
    {
        string GetName();
    }

    public class NameService : INameService
    {
        int idx = 0;

        public string GetName()
        {
            return $"Name_{Interlocked.Increment(ref idx)}";
        }
    }

    public interface IISBNService
    {
        string GetISBN();
    }

    public class ISBNService : IISBNService
    {
        int idx = 0;

        public string GetISBN()
        {
            return $"ISBN_{Interlocked.Increment(ref idx)}_{Guid.NewGuid():N}";
        }
    }

    public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Author { get; set; }
        public string ISBN { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
    }

}

 

posted @ 2025-09-25 12:49  FredGrit  阅读(6)  评论(0)    收藏  举报