WPF mvvm datagrid export as pdf via iTextSharp
Install-Package iTextSharp;
public ICommand ExportAsPDFCommand { get; private set; } ExportAsPDFCommand = new DelCommand(async (obj) => await ExportAsPDFCommandExecuted(obj)); private async Task ExportAsPDFCommandExecuted(object? obj) { try { exportedIdx = 0; var dg = obj as DataGrid; var items = dg.Items.Cast<Book>()?.ToList(); batchCount = items.Count() % batchSize > 0 ? items.Count() / batchSize + 1 : items.Count() / batchSize; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = $"PDF Files|*.pdf"; dialog.FileName = $"PDF_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}_{Guid.NewGuid():N}"; if (dialog.ShowDialog() == true) { await Task.Run(async () => { await ExportListDataInPDFAsync(items, dialog.FileName); await Application.Current.Dispatcher.InvokeAsync(() => { MessageBox.Show($"Exported file in {dialog.FileName}!"); }); }); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private async Task ExportListDataInPDFAsync<T>(List<T> dataList, string pdfFileName) { await Task.Run(async() => { exportedIdx = 0; await PopulateStatusMsg($"Start exporting in pdf"); //Create document and writer using (var pdfDoc = new Document()) { using (var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream(pdfFileName, FileMode.Create))) { pdfDoc.Open(); // Add title var titleFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 18); var titleParagraph = new Paragraph($"Data Export - {typeof(T).Name}", titleFont) { Alignment = Element.ALIGN_CENTER, SpacingAfter = 20f }; pdfDoc.Add(titleParagraph); // Add export date var dateFont = FontFactory.GetFont(FontFactory.HELVETICA, 10); var dateParagraph = new Paragraph($"Exported on: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", dateFont) { Alignment = Element.ALIGN_RIGHT, SpacingAfter = 15f }; pdfDoc.Add(dateParagraph); if (dataList == null || !dataList.Any()) { // Handle empty list var emptyFont = FontFactory.GetFont(FontFactory.HELVETICA_OBLIQUE, 12); var emptyParagraph = new Paragraph("No data available to export.", emptyFont) { Alignment = Element.ALIGN_CENTER, SpacingAfter = 20f }; pdfDoc.Add(emptyParagraph); } else { // Create table with columns based on object properties var props = typeof(T).GetProperties(); var table = new PdfPTable(props.Length) { WidthPercentage = 100, SpacingBefore = 10f, SpacingAfter = 10f }; // Set column widths (optional - adjust as needed) float[] columnWidths = new float[props.Length]; for (int i = 0; i < props.Length; i++) { columnWidths[i] = 1f; } table.SetWidths(columnWidths); // Add header row var headerFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 10); foreach (var prop in props) { var cell = new PdfPCell(new Phrase(prop.Name, headerFont)) { BackgroundColor = new BaseColor(220, 220, 220), HorizontalAlignment = Element.ALIGN_CENTER, Padding = 5f }; table.AddCell(cell); } // Add data rows var dataFont = FontFactory.GetFont(FontFactory.HELVETICA, 9); foreach (var item in dataList) { foreach (var property in props) { var value = property.GetValue(item)?.ToString() ?? string.Empty; var cell = new PdfPCell(new Phrase(value, dataFont)) { Padding = 4f, HorizontalAlignment =(int) HorizontalAlignment.Center }; table.AddCell(cell); } if(++exportedIdx%100000==0) { await PopulateStatusMsg($"Exported {exportedIdx} items in pdf"); } } await PopulateStatusMsg($"Adding table..."); pdfDoc.Add(table); // Add summary var summaryFont = FontFactory.GetFont(FontFactory.HELVETICA, 10); var summaryParagraph = new Paragraph($"Total records: {dataList.Count}", summaryFont) { Alignment = Element.ALIGN_RIGHT, SpacingBefore = 10f }; pdfDoc.Add(summaryParagraph); } pdfDoc.Close(); await PopulateStatusMsg($"Expoted in {pdfFileName} pdf file successfully"); } } }); }
//App.xaml <Application x:Class="WpfApp31.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp31"> <Application.Resources> </Application.Resources> </Application> //App.xaml.cs using Microsoft.Extensions.DependencyInjection; using System.Configuration; using System.Data; using System.Windows; namespace WpfApp31 { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { IServiceProvider 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>(); } } } //MainWindow.xaml <Window x:Class="WpfApp31.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:WpfApp31" WindowState="Maximized" mc:Ignorable="d" Title="{Binding MainTitle}" Height="450" Width="800"> <Grid> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <DataGrid Grid.Row="0" ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLength="2,2" VirtualizingPanel.CacheLengthUnit="Item" ScrollViewer.IsDeferredScrollingEnabled="True" ScrollViewer.CanContentScroll="True" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.Columns> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Grid Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource AncestorType=Window}}" Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource AncestorType=Window}}"> <Grid.Background> <ImageBrush ImageSource="{Binding ImgSource}" Stretch="Uniform"/> </Grid.Background> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.Resources> <Style TargetType="TextBlock"> <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> </Grid.Resources> <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 ISBN}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Center"/> </Grid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> <DataGrid.ContextMenu> <ContextMenu> <MenuItem Header="Export As Excel" Command="{Binding ExportAsExcelCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},Path=PlacementTarget}"/> <MenuItem Header="Export As PDF" Command="{Binding ExportAsPDFCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},Path=PlacementTarget}"/> </ContextMenu> </DataGrid.ContextMenu> </DataGrid> <TextBlock Grid.Row="1" Text="{Binding StatusMsg}" FontSize="30"/> </Grid> </Window> //MainWindow.xaml.cs using CommunityToolkit.Mvvm.ComponentModel; using iTextSharp.text; using iTextSharp.text.pdf; using Microsoft.Win32; using OfficeOpenXml; using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Reflection.Metadata; 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 Document = iTextSharp.text.Document; using Paragraph = iTextSharp.text.Paragraph; namespace WpfApp31 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow(MainVM vm) { InitializeComponent(); this.DataContext = vm; this.Loaded += async (s, e) => { vm.GridWidth = this.ActualWidth; vm.GridHeight = this.ActualHeight / 2; await vm.InitPopulateAsync(); }; } } public partial class MainVM : ObservableObject { IIDService idService; INameService nameService; IISBNService isbnService; List<string> imgsList; int imgsCount = 0; int imgsIdx = 0; int batchSize = 1_000_000; int batchCount = 0; int exportedIdx = 0; private ConcurrentDictionary<string, ImageSource> imgDicCache = new ConcurrentDictionary<string, ImageSource>(); public ICommand ExportAsExcelCommand { get; private set; } public ICommand ExportAsPDFCommand { get; private set; } public MainVM(IIDService idServiceValue, INameService nameServiceValue, IISBNService isbnServiceValue) { idService = idServiceValue; nameService = nameServiceValue; isbnService = isbnServiceValue; MainTitle = $"Now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")}"; ExportAsExcelCommand = new DelCommand(async (obj) => await ExportAsExcelCommandExecuted(obj)); ExportAsPDFCommand = new DelCommand(async (obj) => await ExportAsPDFCommandExecuted(obj)); InitImgsList(); Task.Run(() => { InitTimer(); }); } private async Task ExportAsPDFCommandExecuted(object? obj) { try { exportedIdx = 0; var dg = obj as DataGrid; var items = dg.Items.Cast<Book>()?.ToList(); batchCount = items.Count() % batchSize > 0 ? items.Count() / batchSize + 1 : items.Count() / batchSize; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = $"PDF Files|*.pdf"; dialog.FileName = $"PDF_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}_{Guid.NewGuid():N}"; if (dialog.ShowDialog() == true) { await Task.Run(async () => { await ExportListDataInPDFAsync(items, dialog.FileName); await Application.Current.Dispatcher.InvokeAsync(() => { MessageBox.Show($"Exported file in {dialog.FileName}!"); }); }); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private async Task ExportListDataInPDFAsync<T>(List<T> dataList, string pdfFileName) { await Task.Run(async() => { exportedIdx = 0; await PopulateStatusMsg($"Start exporting in pdf"); //Create document and writer using (var pdfDoc = new Document()) { using (var pdfWriter = PdfWriter.GetInstance(pdfDoc, new FileStream(pdfFileName, FileMode.Create))) { pdfDoc.Open(); // Add title var titleFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 18); var titleParagraph = new Paragraph($"Data Export - {typeof(T).Name}", titleFont) { Alignment = Element.ALIGN_CENTER, SpacingAfter = 20f }; pdfDoc.Add(titleParagraph); // Add export date var dateFont = FontFactory.GetFont(FontFactory.HELVETICA, 10); var dateParagraph = new Paragraph($"Exported on: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", dateFont) { Alignment = Element.ALIGN_RIGHT, SpacingAfter = 15f }; pdfDoc.Add(dateParagraph); if (dataList == null || !dataList.Any()) { // Handle empty list var emptyFont = FontFactory.GetFont(FontFactory.HELVETICA_OBLIQUE, 12); var emptyParagraph = new Paragraph("No data available to export.", emptyFont) { Alignment = Element.ALIGN_CENTER, SpacingAfter = 20f }; pdfDoc.Add(emptyParagraph); } else { // Create table with columns based on object properties var props = typeof(T).GetProperties(); var table = new PdfPTable(props.Length) { WidthPercentage = 100, SpacingBefore = 10f, SpacingAfter = 10f }; // Set column widths (optional - adjust as needed) float[] columnWidths = new float[props.Length]; for (int i = 0; i < props.Length; i++) { columnWidths[i] = 1f; } table.SetWidths(columnWidths); // Add header row var headerFont = FontFactory.GetFont(FontFactory.HELVETICA_BOLD, 10); foreach (var prop in props) { var cell = new PdfPCell(new Phrase(prop.Name, headerFont)) { BackgroundColor = new BaseColor(220, 220, 220), HorizontalAlignment = Element.ALIGN_CENTER, Padding = 5f }; table.AddCell(cell); } // Add data rows var dataFont = FontFactory.GetFont(FontFactory.HELVETICA, 9); foreach (var item in dataList) { foreach (var property in props) { var value = property.GetValue(item)?.ToString() ?? string.Empty; var cell = new PdfPCell(new Phrase(value, dataFont)) { Padding = 4f, HorizontalAlignment =(int) HorizontalAlignment.Center }; table.AddCell(cell); } if(++exportedIdx%100000==0) { await PopulateStatusMsg($"Exported {exportedIdx} items in pdf"); } } await PopulateStatusMsg($"Adding table..."); pdfDoc.Add(table); // Add summary var summaryFont = FontFactory.GetFont(FontFactory.HELVETICA, 10); var summaryParagraph = new Paragraph($"Total records: {dataList.Count}", summaryFont) { Alignment = Element.ALIGN_RIGHT, SpacingBefore = 10f }; pdfDoc.Add(summaryParagraph); } pdfDoc.Close(); await PopulateStatusMsg($"Expoted in {pdfFileName} pdf file successfully"); } } }); } private async Task ExportAsExcelCommandExecuted(object? obj) { try { await PopulateStatusMsg($"Start exporting"); exportedIdx = 0; var dg = obj as DataGrid; var items = dg.Items.Cast<Book>()?.ToList(); batchCount = items.Count() % batchSize > 0 ? items.Count() / batchSize + 1 : items.Count() / batchSize; SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = $"Excel Files|*.xlsx"; dialog.FileName = $"Excel_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}_{Guid.NewGuid():N}"; if (dialog.ShowDialog() == true) { await Task.Run(async () => { await ExportListDataInExcelAsync(items, dialog.FileName); await Application.Current.Dispatcher.InvokeAsync(() => { MessageBox.Show($"Exported file in {dialog.FileName}!"); }); }); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } private async Task ExportListDataInExcelAsync<T>(List<T> dataList, string excelFileName) { EPPlusLicense license = new EPPlusLicense(); license.SetNonCommercialPersonal("Fred"); using (ExcelPackage package = new ExcelPackage()) { var props = typeof(T).GetProperties(); for (int i = 0; i < batchCount; i++) { var tempBooksList = dataList.Skip(i * batchSize).Take(batchSize).ToList(); var workSheet = package.Workbook.Worksheets.Add($"Sheet_{i + 1}"); for (int j = 0; j < props.Count(); j++) { workSheet.Cells[1, j + 1].Value = props[j].Name; } for (int row = 0; row < tempBooksList.Count; row++) { ++exportedIdx; for (int col = 0; col < props.Length; col++) { var value = props[col].GetValue(tempBooksList[row]); workSheet.Cells[row + 2, col + 1].Value = value; } } // Format headers using (var range = workSheet.Cells[1, 1, 1, props.Length]) { range.Style.Font.Bold = true; range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid; range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue); } // Auto-fit columns workSheet.Cells[workSheet.Dimension.Address].AutoFitColumns(); await PopulateStatusMsg($"Exported {exportedIdx} items"); } await PopulateStatusMsg($"Writing in {excelFileName} items"); package.SaveAs(new FileInfo(excelFileName)); } await PopulateStatusMsg($"Exported in {excelFileName} successfully!"); } private async Task PopulateStatusMsg(string msgValue) { await Application.Current.Dispatcher.InvokeAsync(() => { StatusMsg = msgValue; }); } private void InitImgsList() { var imgDir = @"../../../Images"; if (!Directory.Exists(imgDir)) { return; } imgsList = new List<string>(Directory.GetFiles(imgDir)); imgsCount = imgsList.Count; } public async Task InitPopulateAsync() { BooksCollection = new ObservableCollection<Book>(); List<Book> booksList = new List<Book>(); for (int i = 1; i < 1000101; i++) { booksList.Add(new Book() { Id = idService.GetID(), Name = nameService.GetName(), ISBN = isbnService.GetISBN(), Title = $"Title_{i}", Topic = $"Topic_{i}", ImgSource = GetImgSourceViaUrl(imgsList[imgsIdx % imgsCount]) }); ++imgsIdx; if (i % 100000 == 0) { await PopulateBooksCollectionAsync(booksList); } } if (booksList.Any()) { await PopulateBooksCollectionAsync(booksList); } StatusMsg = $"Loaded completely,now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")}," + $"loaded {BooksCollection.Count} items,memory:{GetMemory()}"; } private ImageSource GetImgSourceViaUrl(string imgUrl) { if (!File.Exists(imgUrl)) { return null; } if (imgDicCache.TryGetValue(imgUrl, out var img)) { return img; } BitmapImage bmi = new BitmapImage(); bmi.BeginInit(); bmi.UriSource = new Uri(imgUrl, UriKind.RelativeOrAbsolute); bmi.EndInit(); bmi.CacheOption = BitmapCacheOption.OnDemand; if (bmi.CanFreeze) { bmi.Freeze(); } imgDicCache[imgUrl] = bmi; return bmi; } private async Task PopulateBooksCollectionAsync(List<Book> booksList) { var tempBooks = booksList.ToList(); booksList.Clear(); await Application.Current.Dispatcher.InvokeAsync(() => { foreach (var bk in tempBooks) { BooksCollection.Add(bk); } StatusMsg = $"Now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")}," + $"loaded {BooksCollection.Count} items,memory:{GetMemory()}"; }, System.Windows.Threading.DispatcherPriority.Background); } private string GetMemory() { var memory = Process.GetCurrentProcess().PrivateMemorySize64; return $"{(memory / 1024 / 1024).ToString("#,##0.00")} M"; } 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 = $"Now is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.ffff")}"; } [ObservableProperty] private ObservableCollection<Book> booksCollection; [ObservableProperty] private string mainTitle; [ObservableProperty] private double gridWidth; [ObservableProperty] private double gridHeight; [ObservableProperty] private string statusMsg; } 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); } } public class Book { public int Id { get; set; } public string Name { get; set; } public string ISBN { get; set; } public string Title { get; set; } public string Topic { get; set; } public ImageSource ImgSource { get; set; } } public interface IIDService { int GetID(); } public class IDService : IIDService { int idx = 0; public int GetID() { return Interlocked.Increment(ref idx); } } public interface INameService { string GetName(); } public class NameService : INameService { int idx = 0; public string GetName() { return $"Name_{++idx}"; } } public interface IISBNService { string GetISBN(); } public class ISBNService : IISBNService { int idx = 0; public string GetISBN() { return $"ISBN_{++idx}_{Guid.NewGuid():N}"; } } }