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;
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

image

 

 

 

//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);
        }
    }
}

 

 

image

 

posted @ 2025-10-05 22:09  FredGrit  阅读(7)  评论(0)    收藏  举报