WPF datagrid export multiple specified datagridrows into one picture by System.Drawing.Common graphics without blocking ui issues
Install-Package System.Drawing.Common;
public ICommand SaveAsJPGSCommand { get { if (saveAsJPGSCommand==null) { saveAsJPGSCommand=new DelCommand(async (obj) => await SaveAsJPGSCommandExecutedAsync(obj)); } return saveAsJPGSCommand; } } private async Task SaveAsJPGSCommandExecutedAsync(object? obj) { int batchIdx = 0; string dir = "", groupDir = ""; try { var dg = obj as DataGrid; if (dg==null) { return; } dir = $"JPG_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } groupDir = $"Group_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}"; if (!Directory.Exists(groupDir)) { Directory.CreateDirectory(groupDir); } var items = dg.Items; int idx = 0; foreach (var item in items) { await Application.Current.Dispatcher.InvokeAsync(() => { dg.ScrollIntoView(item); dg.UpdateLayout(); var row = dg.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; if (row != null) { var bmp = SaveDataGridRowAsRenderTargetBitmap(row); string jpgFile = Path.Combine(dir, $"{++idx}.jpg"); SaveBitmapSourceAsJPGFile(bmp, jpgFile); } }); } //MessageBox.Show($"Saved {idx} files in {dir}"); await Task.Run(() => { var imgs = Directory.GetFiles(dir) .Select(x => new FileInfo(x)) .OrderBy(x => x.CreationTime) .Select(x => x.FullName); var imgsGroups = imgs.Chunk(20); batchIdx=0; foreach (var imgGroup in imgsGroups) { List<Bitmap> bitmapsList = new List<Bitmap>(); foreach (var bitmap in imgGroup) { bitmapsList.Add(new Bitmap(bitmap)); } using (var bmp = ImageStitcher.SticthImagesVertical(bitmapsList)) { string groupedImgFile = System.IO.Path.Combine(groupDir, $"Group_{++batchIdx}.jpg"); bmp.Save(groupedImgFile, System.Drawing.Imaging.ImageFormat.Jpeg); } foreach (var bmp in bitmapsList) { bmp.Dispose(); } } }); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } finally { await Task.Run(() => { try { if (Directory.Exists(dir)) { // Ensure all file handles are closed GC.Collect(); GC.WaitForPendingFinalizers(); Thread.Sleep(100); //Directory.Delete(dir, true); } } catch (IOException ex) { System.Diagnostics.Debug.WriteLine($"Could not delete temp directory: {ex.Message}"); } }); MessageBox.Show($"Saved {batchIdx} grouped images in {groupDir}"); } } private void SaveBitmapSourceAsJPGFile(RenderTargetBitmap rtb, string jpgFile) { using (FileStream fileStream = new FileStream(jpgFile, FileMode.Create)) { JpegBitmapEncoder encoder = new JpegBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); encoder.Save(fileStream); } } private RenderTargetBitmap SaveDataGridRowAsRenderTargetBitmap(DataGridRow row) { var rtb = new RenderTargetBitmap( (int)row.ActualWidth*2, (int)row.ActualHeight*2, 96*2, 96*2, PixelFormats.Pbgra32); DrawingVisual drawingVisual = new DrawingVisual(); using (DrawingContext drawingContext = drawingVisual.RenderOpen()) { VisualBrush brush = new VisualBrush(row); drawingContext.DrawRectangle(brush, null, new Rect(0, 0, row.ActualWidth, row.ActualHeight)); } rtb.Render(drawingVisual); //rtb.Freeze(); return rtb; } public static class ImageStitcher { public static Bitmap SticthImagesVertical(List<Bitmap> imgsList) { if (imgsList==null && !imgsList.Any()) { return null; } int width = 0; int height = 0; foreach (var img in imgsList) { if (img.Width>width) { width=img.Width; } height+=img.Height; } Bitmap finalImage = new Bitmap(width, height); using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.White); int offsetY = 0; foreach (var img in imgsList) { g.DrawImage(img, new System.Drawing.Rectangle(0, offsetY, img.Width, img.Height)); offsetY+=img.Height; } } return finalImage; } }
//xaml <Window x:Class="WpfApp64.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:WpfApp64" WindowState="Maximized" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <DataGrid x:Name="dg" ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ColumnWidth="Auto" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.Resources> <Style TargetType="DataGridRow"> <Setter Property="FontSize" Value="30"/> </Style> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Id}"/> <DataGridTextColumn Binding="{Binding Name}"/> <DataGridTextColumn Binding="{Binding Author}"/> <DataGridTextColumn Binding="{Binding Title}"/> <DataGridTextColumn Binding="{Binding Topic}"/> <DataGridTextColumn Binding="{Binding Summary}"/> <DataGridTextColumn Binding="{Binding ISBN}"/> <DataGridTextColumn Binding="{Binding Content}"/> <DataGridTextColumn Binding="{Binding Chapter}"/> <DataGridTextColumn 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.Drawing; using System.IO; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using Color = System.Drawing.Color; namespace WpfApp64 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); 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) { await Task.Run(() => { InitData(); }); } private void InitData() { int idx = 0; var tempList = new List<Book>(); for (int i = 0; i<1000; 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 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 SaveAsJPGSCommandExecutedAsync(obj)); } return saveAsJPGSCommand; } } private async Task SaveAsJPGSCommandExecutedAsync(object? obj) { int batchIdx = 0; string dir = "", groupDir = ""; try { var dg = obj as DataGrid; if (dg==null) { return; } dir = $"JPG_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}"; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } groupDir = $"Group_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}"; if (!Directory.Exists(groupDir)) { Directory.CreateDirectory(groupDir); } var items = dg.Items; int idx = 0; foreach (var item in items) { await Application.Current.Dispatcher.InvokeAsync(() => { dg.ScrollIntoView(item); dg.UpdateLayout(); var row = dg.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; if (row != null) { var bmp = SaveDataGridRowAsRenderTargetBitmap(row); string jpgFile = Path.Combine(dir, $"{++idx}.jpg"); SaveBitmapSourceAsJPGFile(bmp, jpgFile); } }); } //MessageBox.Show($"Saved {idx} files in {dir}"); await Task.Run(() => { var imgs = Directory.GetFiles(dir) .Select(x => new FileInfo(x)) .OrderBy(x => x.CreationTime) .Select(x => x.FullName); var imgsGroups = imgs.Chunk(20); batchIdx=0; foreach (var imgGroup in imgsGroups) { List<Bitmap> bitmapsList = new List<Bitmap>(); foreach (var bitmap in imgGroup) { bitmapsList.Add(new Bitmap(bitmap)); } using (var bmp = ImageStitcher.SticthImagesVertical(bitmapsList)) { string groupedImgFile = System.IO.Path.Combine(groupDir, $"Group_{++batchIdx}.jpg"); bmp.Save(groupedImgFile, System.Drawing.Imaging.ImageFormat.Jpeg); } foreach (var bmp in bitmapsList) { bmp.Dispose(); } } }); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } finally { await Task.Run(() => { try { if (Directory.Exists(dir)) { // Ensure all file handles are closed GC.Collect(); GC.WaitForPendingFinalizers(); Thread.Sleep(100); //Directory.Delete(dir, true); } } catch (IOException ex) { System.Diagnostics.Debug.WriteLine($"Could not delete temp directory: {ex.Message}"); } }); MessageBox.Show($"Saved {batchIdx} grouped images in {groupDir}"); } } private void SaveBitmapSourceAsJPGFile(RenderTargetBitmap rtb, string jpgFile) { using (FileStream fileStream = new FileStream(jpgFile, FileMode.Create)) { JpegBitmapEncoder encoder = new JpegBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(rtb)); encoder.Save(fileStream); } } private RenderTargetBitmap SaveDataGridRowAsRenderTargetBitmap(DataGridRow row) { var rtb = new RenderTargetBitmap( (int)row.ActualWidth*2, (int)row.ActualHeight*2, 96*2, 96*2, PixelFormats.Pbgra32); DrawingVisual drawingVisual = new DrawingVisual(); using (DrawingContext drawingContext = drawingVisual.RenderOpen()) { VisualBrush brush = new VisualBrush(row); drawingContext.DrawRectangle(brush, null, new Rect(0, 0, row.ActualWidth, row.ActualHeight)); } rtb.Render(drawingVisual); //rtb.Freeze(); return rtb; } } public static class ImageStitcher { public static Bitmap SticthImagesVertical(List<Bitmap> imgsList) { if (imgsList==null && !imgsList.Any()) { return null; } int width = 0; int height = 0; foreach (var img in imgsList) { if (img.Width>width) { width=img.Width; } height+=img.Height; } Bitmap finalImage = new Bitmap(width, height); using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.White); int offsetY = 0; foreach (var img in imgsList) { g.DrawImage(img, new System.Drawing.Rectangle(0, offsetY, img.Width, img.Height)); offsetY+=img.Height; } } return finalImage; } } 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); } } }