Install-Package Microsoft.Extensions.DependencyInjection;
Install-Package CommunityToolkit.mvvm;
public async Task InitDataAsync() {watch.Start();string imgDir = @"../../../Images";if (!Directory.Exists(imgDir)){return;}var imgs = Directory.GetFiles(imgDir);if (imgs==null ||!imgs.Any()){return;}int imgsCount = imgs.Count();BooksCollection=new ObservableCollection<Book>();List<Book> booksList = new List<Book>();for (int i = 1; i<100000001; i++){booksList.Add(new Book(){Id=idService.GetID(),Name=nameService.GetName(),ISBN=isbnService.GetISBN(),Author=$"Author_{i}",Title=$"Title_{i}",Topic=$"Topic_{i}",//ImgSource=GetImgSourceViaUrl(imgs[i%imgsCount]),ImgUrl=imgs[i%imgsCount]});if (i<1001 && i%100==0){await PopulateBooksCollectionAsync(booksList);}else if (i>1000 && i%100000==0){await PopulateBooksCollectionAsync(booksList);}}if (booksList.Any()){await PopulateBooksCollectionAsync(booksList);} }private ImageSource GetImgSourceViaUrl(string imgUrl) {if (imgDicCache.TryGetValue(imgUrl, out var img)){return img;}BitmapImage bmi = new BitmapImage();bmi.BeginInit();bmi.CacheOption= BitmapCacheOption.OnDemand;bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute);bmi.EndInit();bmi.Freeze();imgDicCache[imgUrl] = bmi;return bmi; }private async Task PopulateBooksCollectionAsync(List<Book> booksList) {var tempList = booksList.ToList();booksList.Clear();await Application.Current.Dispatcher.InvokeAsync(() =>{foreach (var bk in tempList){BooksCollection.Add(bk);}StatusMsg=$"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";}, System.Windows.Threading.DispatcherPriority.Background);// Yield to keep UI responsive//await Task.Delay(1); }private string GetMemory() {var memory = Process.GetCurrentProcess().PrivateMemorySize64/1024.0d/1024.0d;return $"Memory:{memory.ToString("#,##0")} M"; }private string GetTimeCost() {return $"Time cost:{watch.Elapsed.TotalSeconds} seconds!"; }
//app.xaml <Application x:Class="WpfApp30.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp30"><Application.Resources></Application.Resources> </Application>//App.xaml.cs using Microsoft.Extensions.DependencyInjection; using System.Configuration; using System.Data; using System.Windows;namespace WpfApp30 {/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App : Application{ServiceProvider serviceProvider;protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);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){base.OnExit(e);serviceProvider?.Dispose();}}}//MainWindow.xaml <Window x:Class="WpfApp30.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:WpfApp30"WindowState="Maximized"mc:Ignorable="d"Title="{Binding StatusMsg,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"Height="450" Width="800"><Window.Resources><local:ImgPathConverter x:Key="ImgPathConverter"/></Window.Resources><Grid><ListBox ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"VirtualizingPanel.IsVirtualizing="True"VirtualizingPanel.VirtualizationMode="Recycling"VirtualizingPanel.CacheLengthUnit="Pixel"ScrollViewer.CanContentScroll="True"ScrollViewer.IsDeferredScrollingEnabled="True"Background="Transparent"><ListBox.ItemTemplate><DataTemplate><Grid Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource AncestorType=Window}}"Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource AncestorType=Window}}"><Grid.Background><ImageBrush ImageSource="{Binding ImgUrl,Converter={StaticResource ImgPathConverter}}"/></Grid.Background><Grid.Resources><Style TargetType="TextBlock"><Setter Property="FontSize" Value="30"/><Setter Property="Effect"><Setter.Value><DropShadowEffect ShadowDepth="1" Color="Black"Opacity="0.8"/></Setter.Value></Setter><Style.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter Property="FontSize" Value="50"/><Setter Property="Foreground" Value="Red"/></Trigger></Style.Triggers></Style></Grid.Resources><Grid.RowDefinitions><RowDefinition/><RowDefinition/></Grid.RowDefinitions><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/><ColumnDefinition/></Grid.ColumnDefinitions><TextBlock Text="{Binding Id}" Grid.Row="0" Grid.Column="0"/><TextBlock Text="{Binding Name}" Grid.Row="0" Grid.Column="1"/><TextBlock Text="{Binding Title}" Grid.Row="0" Grid.Column="2"/><TextBlock Text="{Binding Topic}" Grid.Row="0" Grid.Column="3"/><TextBlock Text="{Binding Author}" Grid.Row="0" Grid.Column="4"/><TextBlock Text="{Binding ISBN}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="5"/></Grid></DataTemplate></ListBox.ItemTemplate></ListBox></Grid> </Window>//MainWindow.xaml.cs using CommunityToolkit.Mvvm.ComponentModel; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Runtime.Intrinsics.X86; 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.Interop; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes;namespace WpfApp30 {/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{public MainWindow(){InitializeComponent();}public MainWindow(MainVM vm){InitializeComponent();RenderOptions.ProcessRenderMode=RenderMode.Default;this.DataContext=vm;this.Loaded+=async (s, e) =>{await LoadDataAsync(s, e);};this.SizeChanged+=MainWindow_SizeChanged;}private async Task LoadDataAsync(object s, RoutedEventArgs e){if (DataContext is MainVM vm){await vm.InitDataAsync();}}private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e){if (this.DataContext is MainVM vm){var fe = this.Content as FrameworkElement;if (fe!=null){vm.GridWidth=fe.ActualWidth;vm.GridHeight=fe.ActualHeight/2;}}}}public partial class MainVM : ObservableObject{IIDService idService;INameService nameService;IISBNService isbnService;private Dictionary<string, ImageSource> imgDicCache = new Dictionary<string, ImageSource>();private Stopwatch watch = new Stopwatch();public MainVM(IIDService idServiceValue, INameService nameServiceValue, IISBNService isbnServiceValue){idService = idServiceValue;nameService = nameServiceValue;isbnService = isbnServiceValue;}public async Task InitDataAsync(){watch.Start();string imgDir = @"../../../Images";if (!Directory.Exists(imgDir)){return;}var imgs = Directory.GetFiles(imgDir);if (imgs==null ||!imgs.Any()){return;}int imgsCount = imgs.Count();BooksCollection=new ObservableCollection<Book>();List<Book> booksList = new List<Book>();for (int i = 1; i<100000001; i++){booksList.Add(new Book(){Id=idService.GetID(),Name=nameService.GetName(),ISBN=isbnService.GetISBN(),Author=$"Author_{i}",Title=$"Title_{i}",Topic=$"Topic_{i}",//ImgSource=GetImgSourceViaUrl(imgs[i%imgsCount]),ImgUrl=imgs[i%imgsCount]});if (i<1001 && i%100==0){await PopulateBooksCollectionAsync(booksList);}else if (i>1000 && i%100000==0){await PopulateBooksCollectionAsync(booksList);}}if (booksList.Any()){await PopulateBooksCollectionAsync(booksList);}}private ImageSource GetImgSourceViaUrl(string imgUrl){if (imgDicCache.TryGetValue(imgUrl, out var img)){return img;}BitmapImage bmi = new BitmapImage();bmi.BeginInit();bmi.CacheOption= BitmapCacheOption.OnDemand;bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute);bmi.EndInit();bmi.Freeze();imgDicCache[imgUrl] = bmi;return bmi;}private async Task PopulateBooksCollectionAsync(List<Book> booksList){var tempList = booksList.ToList();booksList.Clear();await Application.Current.Dispatcher.InvokeAsync(() =>{foreach (var bk in tempList){BooksCollection.Add(bk);}StatusMsg=$"Loaded {BooksCollection.Count} items,{GetMemory()},{GetTimeCost()}";}, System.Windows.Threading.DispatcherPriority.Background);// Yield to keep UI responsive//await Task.Delay(1); }private string GetMemory(){var memory = Process.GetCurrentProcess().PrivateMemorySize64/1024.0d/1024.0d;return $"Memory:{memory.ToString("#,##0")} M";}private string GetTimeCost(){return $"Time cost:{watch.Elapsed.TotalSeconds} seconds!"; }[ObservableProperty]private double gridWidth;[ObservableProperty]private double gridHeight;[ObservableProperty]private ObservableCollection<Book> booksCollection;[ObservableProperty]private string statusMsg;}public class Book{public int Id { get; set; }public string Name { get; set; }public string Title { get; set; }public string Topic { get; set; }public string Author { get; set; }public string ISBN { get; set; }public string ImgUrl { get; set; } //public ImageSource ImgSource { get; set; } }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 ImgPathConverter : IValueConverter{ConcurrentDictionary<string, ImageSource> dicCache = new ConcurrentDictionary<string, ImageSource>();public object Convert(object value, Type targetType, object parameter, CultureInfo culture){try{if (value is string imgUrl && File.Exists(imgUrl)){if (dicCache.TryGetValue(imgUrl, out ImageSource img)){return img;}BitmapImage bmi = new BitmapImage();bmi.BeginInit();bmi.CacheOption= BitmapCacheOption.OnDemand;bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute);bmi.EndInit();bmi.Freeze();dicCache[imgUrl]=bmi;return bmi;}}catch (Exception ex){return null;}return null;}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}} }