private ICommand saveAsJPGSCommand;
public ICommand SaveAsJPGSCommand
{
get
{
if (saveAsJPGSCommand==null)
{
saveAsJPGSCommand=new DelCommand(async (obj)=> await SaveAsJPGSCommandExecutedAsync2(obj));
}
return saveAsJPGSCommand;
}
}
private async Task SaveAsJPGSCommandExecutedAsync2(object? obj)
{
var dg = obj as DataGrid;
if (dg==null)
{
return;
}
await Task.Yield(); // let WPF finish layout
// Find the headers presenter
var headersPresenter = GetVisualChild<DataGridColumnHeadersPresenter>(dg);
double headerHeight = 0;
if (headersPresenter != null)
{
headerHeight = headersPresenter.ActualHeight;
// fallback: if headers are not measured yet
if (headerHeight <= 0)
{
headersPresenter.UpdateLayout();
headerHeight = headersPresenter.ActualHeight;
}
}
string dir = $"Dg_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
Directory.CreateDirectory(dir);
VirtualizingStackPanel.SetIsVirtualizing(dg, false);
VirtualizingPanel.SetVirtualizationMode(dg, VirtualizationMode.Standard);
dg.UpdateLayout();
double height = 0, width = 0;
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(dg);
if(scrollViewer==null)
{
return;
}
width= scrollViewer.ExtentWidth;
var row = dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
if (row==null)
{
row=dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
dg.UpdateLayout();
}
if (row==null)
{
return;
}
double rowHeight = row.ActualHeight;
height=rowHeight*dg.Items.Count;
int rowsPerPage = (int)Math.Floor((double)(scrollViewer.ActualHeight-headerHeight)/rowHeight)-1;
int totalPages = (int)Math.Ceiling((double)dg.Items.Count/rowsPerPage);
for (int page = 0; page<totalPages; page++)
{
int startIdx = Math.Min((page+1)*rowsPerPage-1,dg.Items.Count-1);
await dg.Dispatcher.InvokeAsync(() =>
{
dg.ScrollIntoView(dg.Items[startIdx]);
dg.UpdateLayout();
}, DispatcherPriority.Background);
var bmp = RenderDataGridPage(dg,width, rowHeight*20);
string jpgFile = Path.Combine(dir, $"Page_{page+1}.jpg");
SaveBitmapSourceAsJPG(bmp, jpgFile);
Debug.WriteLine($"Page {page+1},startIdx {startIdx}");
//Mock the scroll down effect page by page and pause for a second
await Task.Delay(500);
}
MessageBox.Show($"Saved {totalPages} pages to {dir}");
}
private RenderTargetBitmap RenderDataGridPage(DataGrid dg,double width,double height)
{
var rtb = new RenderTargetBitmap((int)width*2,
(int)height*2,
96*2,
96*2,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush brush = new VisualBrush(dg);
drawingContext.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(drawingVisual);
return rtb;
}
private void SaveBitmapSourceAsJPG(RenderTargetBitmap bmpSource, string jpgFile)
{
using (FileStream fileStream = new FileStream(jpgFile, FileMode.Create))
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmpSource));
encoder.Save(fileStream);
}
}
private T GetVisualChild<T>(DependencyObject dObj) where T : Visual
{
int childCount = VisualTreeHelper.GetChildrenCount(dObj);
for (int i = 0; i<childCount; i++)
{
var child = VisualTreeHelper.GetChild(dObj, i);
if (child is T result)
{
return result;
}
var subChild = GetVisualChild<T>(child);
if (subChild != null)
{
return subChild;
}
}
return null;
}
![image]()
![image]()
![image]()
![image]()
//xaml
<Window x:Class="WpfApp65.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:WpfApp65"
WindowState="Maximized"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid x:Name="dg"
ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="{Binding DataContext.DgWidth,RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}}"
AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Resources>
<Style TargetType="DataGridRow">
<Setter Property="FontSize" Value="30"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Author" Binding="{Binding Author}"/>
<DataGridTextColumn Header="Title" Binding="{Binding Title}"/>
<DataGridTextColumn Header="Topic" Binding="{Binding Topic}"/>
<DataGridTextColumn Header="Summary" Binding="{Binding Summary}"/>
<DataGridTextColumn Header="ISBN" Binding="{Binding ISBN}"/>
<DataGridTextColumn Header="Content" Binding="{Binding Content}"/>
<DataGridTextColumn Header="Chapter" Binding="{Binding Chapter}"/>
<DataGridTextColumn Header="Abstract" Binding="{Binding Abstract}"/>
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="Save As Jpgs"
Command="{Binding SaveAsJPGSCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ContextMenu}},Path=PlacementTarget}"/>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Grid>
</Window>
//cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Path = System.IO.Path;
namespace WpfApp65
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
VirtualizingStackPanel.SetIsVirtualizing(dg, false);
VirtualizingPanel.SetVirtualizationMode(dg, VirtualizationMode.Standard);
var vm = new MainVM(this);
this.DataContext = vm;
}
}
public class MainVM : INotifyPropertyChanged
{
private Window win;
public MainVM(Window winValue)
{
win= winValue;
if (win!=null)
{
win.Loaded+=Win_Loaded;
}
}
private async void Win_Loaded(object sender, RoutedEventArgs e)
{
var fe = win.Content as FrameworkElement;
if (fe!=null)
{
DgWidth= fe.ActualWidth;
}
await Task.Run(() =>
{
InitData();
});
}
private void InitData()
{
int idx = 0;
var tempList = new List<Book>();
for (int i = 0; i<2000; i++)
{
++idx;
tempList.Add(new Book()
{
Id=idx,
Author=$"Author_{idx}",
Description=$"Description_{idx}",
Name=$"Name_{idx}",
Title=$"Title_{idx}",
Topic=$"Topic_{idx}",
Summary=$"Summary_{idx}",
Content=$"Content_{idx}",
Chapter=$"Chapter_{idx}",
Abstract=$"Abstract_{idx}",
ISBN=$"ISBN_{idx}_{Guid.NewGuid().ToString("N")}"
});
}
Application.Current.Dispatcher.BeginInvoke(() =>
{
BooksCollection=new ObservableCollection<Book>(tempList);
});
}
public event PropertyChangedEventHandler? PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propName = "")
{
var handler = PropertyChanged;
if (handler!=null)
{
handler?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
private double dgWidth;
public double DgWidth
{
get
{
return dgWidth;
}
set
{
if (value!=dgWidth)
{
dgWidth = value;
OnPropertyChanged();
}
}
}
private ObservableCollection<Book> booksCollection;
public ObservableCollection<Book> BooksCollection
{
get
{
return booksCollection;
}
set
{
if (value!= booksCollection)
{
booksCollection = value;
OnPropertyChanged(nameof(BooksCollection));
}
}
}
private ICommand saveAsJPGSCommand;
public ICommand SaveAsJPGSCommand
{
get
{
if (saveAsJPGSCommand==null)
{
saveAsJPGSCommand=new DelCommand(async (obj)=> await SaveAsJPGSCommandExecutedAsync2(obj));
}
return saveAsJPGSCommand;
}
}
private async Task SaveAsJPGSCommandExecutedAsync2(object? obj)
{
var dg = obj as DataGrid;
if (dg==null)
{
return;
}
await Task.Yield(); // let WPF finish layout
// Find the headers presenter
var headersPresenter = GetVisualChild<DataGridColumnHeadersPresenter>(dg);
double headerHeight = 0;
if (headersPresenter != null)
{
headerHeight = headersPresenter.ActualHeight;
// fallback: if headers are not measured yet
if (headerHeight <= 0)
{
headersPresenter.UpdateLayout();
headerHeight = headersPresenter.ActualHeight;
}
}
string dir = $"Dg_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
Directory.CreateDirectory(dir);
VirtualizingStackPanel.SetIsVirtualizing(dg, false);
VirtualizingPanel.SetVirtualizationMode(dg, VirtualizationMode.Standard);
dg.UpdateLayout();
double height = 0, width = 0;
ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(dg);
if(scrollViewer==null)
{
return;
}
width= scrollViewer.ExtentWidth;
var row = dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
if (row==null)
{
row=dg.ItemContainerGenerator.ContainerFromIndex(0) as DataGridRow;
dg.UpdateLayout();
}
if (row==null)
{
return;
}
double rowHeight = row.ActualHeight;
height=rowHeight*dg.Items.Count;
int rowsPerPage = (int)Math.Floor((double)(scrollViewer.ActualHeight-headerHeight)/rowHeight)-1;
int totalPages = (int)Math.Ceiling((double)dg.Items.Count/rowsPerPage);
for (int page = 0; page<totalPages; page++)
{
int startIdx = Math.Min((page+1)*rowsPerPage-1,dg.Items.Count-1);
await dg.Dispatcher.InvokeAsync(() =>
{
dg.ScrollIntoView(dg.Items[startIdx]);
dg.UpdateLayout();
}, DispatcherPriority.Background);
var bmp = RenderDataGridPage(dg,width, rowHeight*20);
string jpgFile = Path.Combine(dir, $"Page_{page+1}.jpg");
SaveBitmapSourceAsJPG(bmp, jpgFile);
Debug.WriteLine($"Page {page+1},startIdx {startIdx}");
//Mock the scroll down effect page by page and pause for a second
await Task.Delay(500);
}
MessageBox.Show($"Saved {totalPages} pages to {dir}");
}
private RenderTargetBitmap RenderDataGridPage(DataGrid dg,double width,double height)
{
var rtb = new RenderTargetBitmap((int)width*2,
(int)height*2,
96*2,
96*2,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush brush = new VisualBrush(dg);
drawingContext.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(drawingVisual);
return rtb;
}
private void SaveBitmapSourceAsJPG(RenderTargetBitmap bmpSource, string jpgFile)
{
using (FileStream fileStream = new FileStream(jpgFile, FileMode.Create))
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmpSource));
encoder.Save(fileStream);
}
}
private T GetVisualChild<T>(DependencyObject dObj) where T : Visual
{
int childCount = VisualTreeHelper.GetChildrenCount(dObj);
for (int i = 0; i<childCount; i++)
{
var child = VisualTreeHelper.GetChild(dObj, i);
if (child is T result)
{
return result;
}
var subChild = GetVisualChild<T>(child);
if (subChild != null)
{
return subChild;
}
}
return null;
}
}
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public string Abstract { get; set; }
public string Topic { get; set; }
public string Summary { get; set; }
public string Content { get; set; }
public string Chapter { get; set; }
public string ISBN { get; set; }
}
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);
}
}
}