Install-Package Microsoft.Extensions.DependencyInjection;
Install-Package CommunityToolkit.mvvm;
 
 
 
private void InitCommands()
{
    SaveAsPicturesCommand=new DelCommand(async(obj)=> await SaveAsPicturesCommandExecuted(obj));
}
private async Task SaveAsPicturesCommandExecuted(object? obj)
{
    var dg = obj as DataGrid;
    if(dg==null)
    {
        return;
    }
    await Application.Current.Dispatcher.InvokeAsync(() =>
    {
        dg.ScrollIntoView(dg.Items[0]);
        dg.UpdateLayout();
    });
    double dgHeight = dg.ActualHeight;
    var dgFirstItem = dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
    if(dgFirstItem==null)
    {
        return;               
    }
    double rowHeight = dgFirstItem.ActualHeight;
    int itemsPerPage = (int)Math.Floor(dgHeight/rowHeight);
    
    int pages = 0;
    int currentIndex = 0;
    string dgDir = $"DG_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
    if(!Directory.Exists(dgDir))
    {
        Directory.CreateDirectory(dgDir);
    }
    await Application.Current.Dispatcher.InvokeAsync(async () =>
    {                
        while (currentIndex<dg.Items.Count)
        {
            currentIndex = (++pages)*itemsPerPage-1;
            var itemIndex = dg.Items.Count-1>currentIndex ? currentIndex : dg.Items.Count-1;
            dg.ScrollIntoView(dg.Items[itemIndex]);
            dg.UpdateLayout();
            
            string dgJpgName = System.IO.Path.Combine(dgDir, $"{pages}.jpg");
            SaveDGAsPictures(dg, dgJpgName );
            StatusMsg=$"Scroll into index:{itemIndex},page:{pages},picture:{dgJpgName}";
            Debug.WriteLine(StatusMsg);
            await Task.Delay(100);
        }
        MessageBox.Show($"Saved datagrid in {dgDir} completely!");
    });           
}
private void SaveDGAsPictures(DataGrid dg, string dgFileName)
{
     var dpi=VisualTreeHelper.GetDpi(dg);
     var rtb=new RenderTargetBitmap((int)(dg.ActualWidth*dpi.DpiScaleX),
         (int)(dg.ActualHeight*dpi.DpiScaleY),
         dpi.PixelsPerInchX,
         dpi.PixelsPerInchY,
         PixelFormats.Pbgra32);
    rtb.Render(dg);
    using(FileStream fileStream=new FileStream(dgFileName,FileMode.Create))
    {
        JpegBitmapEncoder encoder=new JpegBitmapEncoder();
        encoder.Frames.Add(BitmapFrame.Create(rtb));
        encoder.Save(fileStream); 
    }
}
 
 
![image]()
 
 
![image]()
 
 
![image]()
 
 
 
 
 
 
 
![image]()
 
 
 
//app.cs
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
using System.Data;
using System.Windows;
namespace WpfApp14
{
    /// <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);
        }
    }
}
//xaml
<Window x:Class="WpfApp14.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:WpfApp14"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="{Binding StatusMsg,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
        Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding BooksBindingList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                  Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource AncestorType=Window}}"
                  Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource AncestorType=Window}}"
                  ScrollViewer.IsDeferredScrollingEnabled="True"
                  AutoGenerateColumns="False"
                  VirtualizingPanel.IsVirtualizing="True"
                  VirtualizingPanel.VirtualizationMode="Recycling">
            <DataGrid.Resources>
                <Style TargetType="DataGridRow">
                    <Setter Property="FontSize" Value="30"/>
                    <Setter Property="HorizontalAlignment" Value="Center"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                    <!--<Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="FontSize" Value="40"/>
                            <Setter Property="Foreground" Value="Red"/>
                        </Trigger>
                    </Style.Triggers>-->
                </Style>
            </DataGrid.Resources>
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding Id}"/>
                <DataGridTextColumn Binding="{Binding Name}"/>
                <DataGridTextColumn Binding="{Binding ISBN}"/>
            </DataGrid.Columns>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Save As Pictures"
                              Command="{Binding SaveAsPicturesCommand}"
                              CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},Path=PlacementTarget}"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</Window>
//cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using System.Text;
using System.Threading.Tasks;
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;
namespace WpfApp14
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
        public MainWindow(MainVM vm)
        {
            InitializeComponent();
            this.DataContext=vm;
            this.SizeChanged+=MainWindow_SizeChanged;
        }
        private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            if (DataContext is MainVM vm)
            {
                var fe = this.Content as FrameworkElement;
                if (fe!=null)
                {
                    vm.GridWidth=fe.ActualWidth;
                    vm.GridHeight=fe.ActualHeight;
                }
            }
        }
    }
    public partial class MainVM : ObservableObject
    {
        IIDService idService;
        IISBNService iSBNService;
        INameService nameService;
        public MainVM(IIDService idServiceValue, IISBNService iSBNServiceValue, INameService nameServiceValue)
        {
            idService=idServiceValue;
            iSBNService=iSBNServiceValue;
            nameService=nameServiceValue;
            InitData();
            InitCommands();
        }
        private void InitCommands()
        {
            SaveAsPicturesCommand=new DelCommand(async(obj)=> await SaveAsPicturesCommandExecuted(obj));
        }
        private async Task SaveAsPicturesCommandExecuted(object? obj)
        {
            var dg = obj as DataGrid;
            if(dg==null)
            {
                return;
            }
            await Application.Current.Dispatcher.InvokeAsync(() =>
            {
                dg.ScrollIntoView(dg.Items[0]);
                dg.UpdateLayout();
            });
            double dgHeight = dg.ActualHeight;
            var dgFirstItem = dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
            if(dgFirstItem==null)
            {
                return;               
            }
            double rowHeight = dgFirstItem.ActualHeight;
            int itemsPerPage = (int)Math.Floor(dgHeight/rowHeight);
            
            int pages = 0;
            int currentIndex = 0;
            string dgDir = $"DG_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
            if(!Directory.Exists(dgDir))
            {
                Directory.CreateDirectory(dgDir);
            }
            await Application.Current.Dispatcher.InvokeAsync(async () =>
            {                
                while (currentIndex<dg.Items.Count)
                {
                    currentIndex = (++pages)*itemsPerPage-1;
                    var itemIndex = dg.Items.Count-1>currentIndex ? currentIndex : dg.Items.Count-1;
                    dg.ScrollIntoView(dg.Items[itemIndex]);
                    dg.UpdateLayout();
                    
                    string dgJpgName = System.IO.Path.Combine(dgDir, $"{pages}.jpg");
                    SaveDGAsPictures(dg, dgJpgName );
                    StatusMsg=$"Scroll into index:{itemIndex},page:{pages},picture:{dgJpgName}";
                    Debug.WriteLine(StatusMsg);
                    await Task.Delay(100);
                }
                MessageBox.Show($"Saved datagrid in {dgDir} completely!");
            });           
        }
        private void SaveDGAsPictures(DataGrid dg, string dgFileName)
        {
             var dpi=VisualTreeHelper.GetDpi(dg);
             var rtb=new RenderTargetBitmap((int)(dg.ActualWidth*dpi.DpiScaleX),
                 (int)(dg.ActualHeight*dpi.DpiScaleY),
                 dpi.PixelsPerInchX,
                 dpi.PixelsPerInchY,
                 PixelFormats.Pbgra32);
            rtb.Render(dg);
            using(FileStream fileStream=new FileStream(dgFileName,FileMode.Create))
            {
                JpegBitmapEncoder encoder=new JpegBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(rtb));
                encoder.Save(fileStream); 
            }
        }
        private T GetVisual<T>(DependencyObject parent) where T : DependencyObject
        {
            if (parent==null)
            {
                return null;
            }
            int childrenCount=VisualTreeHelper.GetChildrenCount(parent);
            for(int i=0;i<childrenCount; i++)
            {
                var child=VisualTreeHelper.GetChild(parent,i);
                if(child !=null && child is T result)
                {
                    return result; 
                }
                //Recursively search child's children
                var childVisual = GetVisual<T>(child);
                if(childVisual != null )
                {
                    return childVisual;
                }
            }
            return null;
        }
        private async Task InitData()
        {
            StatusMsg="Loading...";
            BooksBindingList=new BindingList<Book>();
            BooksBindingList.AllowEdit=true;
            BooksBindingList.AllowNew=true;
            BooksBindingList.AllowRemove=true;
            BooksBindingList.AddingNew+=BooksBindingList_AddingNew;
            await Task.Run(async () =>
            {
                List<Book> booksList = new List<Book>();
                for (int i = 0; i<10000; i++)
                {
                    Book bk = new Book()
                    {
                        Id=idService.GetID(),
                        Name=nameService.GetName(),
                        ISBN=iSBNService.GetISBN()
                    };
                    booksList.Add(bk);
                    if (i%100==0)
                    {
                        await PopulateBooksBindingList(booksList);
                    }
                }
                if (booksList.Count>0)
                {
                    await PopulateBooksBindingList(booksList);
                }
                await Application.Current.Dispatcher.InvokeAsync(() =>
                {
                    StatusMsg=$"Loaded {BooksBindingList.Count} items";
                });
            });
            MessageBox.Show("Initialization finished!");
        }
        private async Task PopulateBooksBindingList(List<Book> booksList)
        {
            await Application.Current.Dispatcher.InvokeAsync(() =>
            {
                var tempList = booksList.ToList();
                booksList.Clear();
                foreach (Book bk in tempList)
                {
                    BooksBindingList.Add(bk);
                }
                StatusMsg=$"Loaded {BooksBindingList.Count} items";
            });
            await Task.Delay(1);
        }
        private void BooksBindingList_AddingNew(object? sender, AddingNewEventArgs e)
        {
            BooksBindingList.Add(new Book()
            {
                Id = idService.GetID(),
                Name=nameService.GetName(),
                ISBN = iSBNService.GetISBN()
            });
        }
        [ObservableProperty]
        private double gridWidth;
        [ObservableProperty]
        private double gridHeight;
        [ObservableProperty]
        private BindingList<Book> booksBindingList;
        [ObservableProperty]
        private string statusMsg;
        public ICommand SaveAsPicturesCommand { get; set; }
    }
    public class Book
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string ISBN { get; set; }
    }
    public interface IIDService
    {
        int GetID();
    }
    public class IDService : IIDService
    {
        int id = 0;
        public int GetID()
        {
            return ++id;
        }
    }
    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}";
        }
    }
    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);
        }
    }
}