WPF ContentControl Content Binding
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 header const 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); //Header var 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; } //Data for (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 header var 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 report if (progress != null) progress.Report(100); } // Bulk load rows starting at row 2, col 1 workSheet.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 widths int 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 width for (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); } } }