Install-Package Microsoft.Extensions.DependencyInjection;
Install-Package CommunityToolkit.mvvm;
<Window x:Class="WpfApp25.Views.MainWin"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:WpfApp25.Views"WindowState="Maximized"mc:Ignorable="d"Title="{Binding MainTitle}" Height="450" Width="800"><Grid><Grid.RowDefinitions><RowDefinition/></Grid.RowDefinitions><ContentControl Grid.Row="0"Content="{Binding DgViewInstance}"x:Name="MainRegion"/></Grid> </Window>using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using WpfApp25.Views;namespace WpfApp25.ViewModels {public partial class MainWinViewModel:ObservableObject{public MainWinViewModel(DgViewModel dgVM){MainTitle = $"In Main Win,now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";InitTimer();DgViewInstance = new DgView(dgVM);}private void InitTimer(){System.Timers.Timer tmr = new System.Timers.Timer();tmr.Interval = 1000;tmr.Elapsed += Tmr_Elapsed;tmr.Start();}private void Tmr_Elapsed(object? sender, System.Timers.ElapsedEventArgs e){MainTitle = $"In Main Win,now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";}[ObservableProperty]private string mainTitle;[ObservableProperty]private string statusMsg;[ObservableProperty]private object dgViewInstance;} }
//App.xaml <Application x:Class="WpfApp25.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp25"><Application.Resources></Application.Resources> </Application>//App.xaml.cs using Microsoft.Extensions.DependencyInjection; using System.Configuration; using System.Data; using System.Windows; using System.Windows.Controls; using WpfApp25.Services; using WpfApp25.ViewModels; using WpfApp25.Views;namespace WpfApp25 {/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App : Application{ServiceProvider serviceProvider;protected override void OnStartup(StartupEventArgs e){base.OnStartup(e);ServiceCollection services = new ServiceCollection();ConfigureServices(services);serviceProvider = services.BuildServiceProvider();var mainWin = serviceProvider.GetRequiredService<MainWin>();mainWin?.Show();}private static void ConfigureServices(ServiceCollection services){services.AddSingleton<IIDService, IDService>();services.AddSingleton<IISBNService, ISBNService>();services.AddSingleton<INameService, NameService>();services.AddSingleton<DgView>();services.AddSingleton<DgViewModel>();services.AddSingleton<MainWin>();services.AddSingleton<MainWinViewModel>();}protected override void OnExit(ExitEventArgs e){base.OnExit(e);serviceProvider?.Dispose();serviceProvider = null;}}}//MainWindow.xaml <Window x:Class="WpfApp25.Views.MainWin"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:WpfApp25.Views"WindowState="Maximized"mc:Ignorable="d"Title="{Binding MainTitle}" Height="450" Width="800"><Grid><Grid.RowDefinitions><RowDefinition/></Grid.RowDefinitions><ContentControl Grid.Row="0"Content="{Binding DgViewInstance}"x:Name="MainRegion"/></Grid> </Window>//MainWin.xaml.cs using System; using System.Collections.Generic; 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.Shapes; using WpfApp25.ViewModels;namespace WpfApp25.Views {/// <summary>/// Interaction logic for MainWin.xaml/// </summary>public partial class MainWin : Window{public MainWin(MainWinViewModel vm){InitializeComponent();this.DataContext = vm;}} }//MainWinViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; using System; using System.Collections.Generic; using System.ComponentModel; using System.Text; using WpfApp25.Views;namespace WpfApp25.ViewModels {public partial class MainWinViewModel:ObservableObject{public MainWinViewModel(DgViewModel dgVM){MainTitle = $"In Main Win,now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";InitTimer();DgViewInstance = new DgView(dgVM);}private void InitTimer(){System.Timers.Timer tmr = new System.Timers.Timer();tmr.Interval = 1000;tmr.Elapsed += Tmr_Elapsed;tmr.Start();}private void Tmr_Elapsed(object? sender, System.Timers.ElapsedEventArgs e){MainTitle = $"In Main Win,now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}";}[ObservableProperty]private string mainTitle;[ObservableProperty]private string statusMsg;[ObservableProperty]private object dgViewInstance;} }//DgView.xaml <UserControl x:Class="WpfApp25.Views.DgView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfApp25.Views"mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"><Grid><Grid.RowDefinitions><RowDefinition/><RowDefinition Height="Auto"/></Grid.RowDefinitions><DataGrid Grid.Row="0"ItemsSource="{Binding BooksCollection}"VirtualizingPanel.IsVirtualizing="True"VirtualizingPanel.VirtualizationMode="Recycling"VirtualizingPanel.CacheLengthUnit="Item"VirtualizingPanel.CacheLength="3,3"ScrollViewer.IsDeferredScrollingEnabled="True"ScrollViewer.CanContentScroll="True"AutoGenerateColumns="False"CanUserAddRows="False"><DataGrid.Resources><Style TargetType="DataGridRow"><Setter Property="FontSize" Value="30"/><Style.Triggers><Trigger Property="IsMouseOver" Value="True"><Setter Property="FontSize" Value="50"/><Setter Property="Foreground" Value="Red"/></Trigger></Style.Triggers></Style></DataGrid.Resources><DataGrid.Columns><!--Author,Name,Comment,Content,ISBN,Title,Topic--><DataGridTextColumn Binding="{Binding Id}"/><DataGridTextColumn Binding="{Binding Author}"/><DataGridTextColumn Binding="{Binding Name}"/><DataGridTextColumn Binding="{Binding Comment}"/><DataGridTextColumn Binding="{Binding Content}"/><DataGridTextColumn Binding="{Binding ISBN}"/><DataGridTextColumn Binding="{Binding Title}"/><DataGridTextColumn Binding="{Binding Topic}"/></DataGrid.Columns><DataGrid.ContextMenu><ContextMenu><MenuItem Header="Export In Excel"Command="{Binding ExportInExcelCommand}"CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},Path=PlacementTarget}"/></ContextMenu></DataGrid.ContextMenu></DataGrid><TextBlock Grid.Row="1"Text="{Binding LoadingMsg}"FontSize="30"/></Grid> </UserControl>//Dgview.xaml.cs using System; using System.Collections.Generic; 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 WpfApp25.ViewModels;namespace WpfApp25.Views {/// <summary>/// Interaction logic for DgView.xaml/// </summary>public partial class DgView : UserControl{public DgView(DgViewModel vm){InitializeComponent();this.DataContext = vm;this.Loaded += async (s, e) =>{await vm.InitBooksCollectionAsync();};}} }//DgViewModel.cs using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.Win32; using OfficeOpenXml; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Text; using System.Windows.Controls; using System.Windows.Input; using WpfApp25.Models; using WpfApp25.Services; using WpfApp25.Utitlity; using OfficeOpenXml; using OfficeOpenXml.Style; using System.Reflection; using System.Windows; using System.Linq; using System.Linq.Expressions; using Expression = System.Linq.Expressions.Expression;namespace WpfApp25.ViewModels {public partial class DgViewModel : ObservableObject{IIDService idService;INameService nameService;IISBNService isbnService; public DgViewModel(IIDService idServiceValue, INameService nameServiceValue, IISBNService isbnServiceValue){idService = idServiceValue;nameService = nameServiceValue;isbnService = isbnServiceValue;ExportInExcelCommand = new DelCommand(ExportInExcelCommandExecuted);}int globalProcessedCount = 0;private void ExportInExcelCommandExecuted(object? obj){var dg = obj as DataGrid;if (dg != null){Task.Run(() =>{var itemsList = dg.Items.Cast<Book>()?.ToList();if (itemsList == null || !itemsList.Any()){Application.Current.Dispatcher.Invoke(() =>{MessageBox.Show("No data to export!");});return;}string fileName = null;Application.Current.Dispatcher.Invoke(() =>{SaveFileDialog dialog = new SaveFileDialog();dialog.Filter = "Excel Files|*.xlsx;*.xls";dialog.FileName = $"Excel_{DateTime.Now:yyyyMMddHHmmss}";if (dialog.ShowDialog() == true){fileName = dialog.FileName;}});if (!string.IsNullOrEmpty(fileName)){var progress = new Progress<int>(percent =>{Application.Current.Dispatcher.Invoke(() =>{LoadingMsg = $"Exporting... {percent}%";});});try{ExportListT(itemsList, fileName, progress);Application.Current.Dispatcher.Invoke(() =>{MessageBox.Show($"Data saved successfully in {fileName}!");LoadingMsg = "Export completed!";});}catch (Exception ex){Application.Current.Dispatcher.Invoke(() =>{MessageBox.Show($"Export failed: {ex.Message}");LoadingMsg = "Export failed!";});}}});}}private void ExportListT<T>(List<T> dataList, string excelFileName, IProgress<int> progress = null){EPPlusLicense license = new EPPlusLicense();license.SetNonCommercialPersonal("Grit");using (var package = new ExcelPackage()){if (dataList == null || !dataList.Any()){var workSheet = package.Workbook.Worksheets.Add("Sheet_1");workSheet.Cells["A1"].Value = "No data available";package.SaveAs(new System.IO.FileInfo(excelFileName));return;}const int excelMaxRows = 1048576;// Reserve 1 row for headerconst int maxDataRows = excelMaxRows - 1;int totalCount = dataList.Count;int processedCount = 0;var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);if (dataList.Count <= maxDataRows){ExportToWorkSheet_Fast(package, "sheet_1", dataList, progress, totalCount);}else{int sheetcount = (int)Math.Ceiling((double)dataList.Count / maxDataRows);for (int sheetIdx = 0; sheetIdx < sheetcount; sheetIdx++){var sheetDataList = dataList.Skip(sheetIdx * maxDataRows).Take(maxDataRows).ToList();ExportToWorkSheet_Fast(package, $"Sheet_{sheetIdx + 1}", sheetDataList, progress, totalCount);}}package.SaveAs(new System.IO.FileInfo(excelFileName));}}private void ExportToWorkSheet<T>(ExcelPackage package, string sheetName, List<T> dataList,IProgress<int> progress = null, int totalCount = 0){var workSheet = package.Workbook.Worksheets.Add(sheetName);//Headervar props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);for (int i = 0; i < props.Length; i++){var cell = workSheet.Cells[1, i + 1];cell.Value = props[i].Name;cell.Style.Font.Bold = true;cell.Style.Fill.PatternType = ExcelFillStyle.Solid;cell.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);cell.Style.Border.Bottom.Style = ExcelBorderStyle.Thick;}//Datafor (int row = 0; row < dataList.Count; row++){++globalProcessedCount;var item = dataList[row];for (int col = 0; col < props.Length; col++){var value = props[col].GetValue(item);workSheet.Cells[row + 2, col + 1].Value = value;if (progress != null && (globalProcessedCount % 10000 == 0 || row == dataList.Count - 1)){int percent = (int)((double)globalProcessedCount / totalCount * 100);progress.Report(Math.Min(percent, 100));LoadingMsg = $"Export processed {percent}%100";}}}workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();}private void ExportToWorkSheet_Fast<T>(ExcelPackage package, string sheetName, List<T> dataList,IProgress<int> progress = null, int totalCount = 0){var workSheet = package.Workbook.Worksheets.Add(sheetName);// Properties and headervar props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);for (int i = 0; i < props.Length; i++){var cell = workSheet.Cells[1, i + 1];cell.Value = props[i].Name;cell.Style.Font.Bold = true;cell.Style.Fill.PatternType = ExcelFillStyle.Solid;cell.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);cell.Style.Border.Bottom.Style = ExcelBorderStyle.Thick;}if (dataList == null || dataList.Count == 0){package.SaveAs(new System.IO.FileInfo("empty.xlsx"));return;}// Build fast delegates for property getters (cached)var getters = new Func<T, object>[props.Length];for (int i = 0; i < props.Length; i++){var p = props[i];// Create a compiled lambda (obj => (object)((T)obj).Prop)var param = Expression.Parameter(typeof(T), "x");var propertyAccess = Expression.Property(param, p);Expression converted = Expression.Convert(propertyAccess, typeof(object));var lambda = Expression.Lambda<Func<T, object>>(converted, param);getters[i] = lambda.Compile();}// Create an IEnumerable<object[]> of rows (deferred streaming)IEnumerable<object[]> Rows(){foreach (var item in dataList){globalProcessedCount++;var row = new object[props.Length];for (int i = 0; i < props.Length; i++){row[i] = getters[i](item);}// report progress periodically (not for every cell)if (progress != null && (globalProcessedCount % 10000 == 0)){int percent = totalCount > 0 ? (int)((double)globalProcessedCount / totalCount * 100) : 0;progress.Report(Math.Min(percent, 100));}yield return row;}// final reportif (progress != null)progress.Report(100);}// Bulk load rows starting at row 2, col 1workSheet.Cells[2, 1].LoadFromArrays(Rows());// Column sizing:// - For small/medium datasets, AutoFitColumns is OK// - For very large datasets (>100k rows) it is very slow. Here I show a sampling approach:try{if (dataList.Count <= 50000){workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns();}else{// AutoFit only based on first N rows + header to get reasonable column widthsint sampleRows = Math.Min(2000, dataList.Count);for (int c = 1; c <= props.Length; c++){var from = workSheet.Cells[1, c, sampleRows + 1, c];//workSheet.Column(c).Width = from.AutoFitColumnsGetWidth(); // EPPlus internal helper? if not available, skip // If AutoFitColumnsGetWidth isn't available in your EPPlus, you can skip or set a default width:// workSheet.Column(c).Width = 20; }}}catch{// If AutoFit fails or is slow, fall back to default widthfor (int c = 1; c <= props.Length; c++)workSheet.Column(c).Width = 20;}}public async Task InitBooksCollectionAsync(){BooksCollection = new ObservableCollection<Book>();List<Book> booksList = new List<Book>();await Task.Run(async () =>{for (int i = 1; i < 3000001; i++){booksList.Add(new Book(){Id = idService.GetID(),Name = nameService.GetName(),ISBN = isbnService.GetISBN(),Author = $"Author_{i}",Comment = $"Comment_{i}",Content = $"Content_{i}",Title = $"Title_{i}",Topic = $"Topic_{i}"});if (i % 100000 == 0){await PopulateBooksCollectionAsync(booksList);}}if (booksList.Any()){await PopulateBooksCollectionAsync(booksList);}LoadingMsg = $"Loading Completed!";});}private async Task PopulateBooksCollectionAsync(List<Book> booksList){var tempList = booksList.ToList();booksList.Clear();await System.Windows.Application.Current.Dispatcher.InvokeAsync(() =>{foreach (var bk in tempList){BooksCollection.Add(bk);}LoadingMsg = $"Loaded {BooksCollection.Count} items";}, System.Windows.Threading.DispatcherPriority.Background);}[ObservableProperty]private ObservableCollection<Book> booksCollection;[ObservableProperty]private string loadingMsg;public ICommand ExportInExcelCommand { get; set; }} }//Book.cs using System; using System.Collections.Generic; using System.Text;namespace WpfApp25.Models {public class Book{public int Id { get; set; }public string Author { get; set; }public string Name { get; set; } public string Comment { get; set; }public string Content { get; set; }public string ISBN { get; set; }public string Title { get; set; }public string Topic { get; set; } } }//services.cs using System; using System.Collections.Generic; using System.Text;namespace WpfApp25.Services {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}";}} }//DelCommand.cs using System; using System.Collections.Generic; using System.Text; using System.Windows.Input;namespace WpfApp25.Utitlity {public class DelCommand : ICommand{private Action<Object?> execute;private Predicate<Object?> canExecute;public DelCommand(Action<Object?> executeValue, Predicate<Object?> canExecuteValue=null){execute = executeValue;canExecute = canExecuteValue;}public event EventHandler? CanExecuteChanged{add{CommandManager.RequerySuggested += value;}remove{CommandManager.RequerySuggested -= value;}}public bool CanExecute(object? parameter){return canExecute == null ? true : canExecute(parameter);}public void Execute(object? parameter){execute(parameter);}} }