WPF DataGrid,mvvm, DI via Microsoft.Extensions.DependencyInjection , ContextMenu save datagridrow as picture without blocking ui issues
Install-Package Microsoft.Extensions.DependencyInjection;
Install-Package CommunityToolKit.mvvm;
private void InitCommands() { SaveListBoxItemsAsPicturesCommand=new RelayCommand<object>(async (obj) => await SaveListBoxItemsAsPicturesCommandExecuted(obj)); } private async Task SaveListBoxItemsAsPicturesCommandExecuted(object? obj) { if (obj==null) { return; } var dg = obj as DataGrid; if (dg==null) { return; } var items = dg.Items.Cast<Book>().ToList(); string jpgDir = $"JPG_{DateTime.Now.ToString("yyyyMMddHHmmsssffff")}"; if (!Directory.Exists(jpgDir)) { Directory.CreateDirectory(jpgDir); } int jpgIdx = 0; foreach (var item in items) { var dgRow = await Application.Current.Dispatcher.InvokeAsync(() => { dg.ScrollIntoView(item); dg.UpdateLayout(); var tempItem = dg.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; return tempItem; }); if (dgRow==null) { continue; } var rtb = await Application.Current.Dispatcher.InvokeAsync(() => { var dpi = VisualTreeHelper.GetDpi(dgRow); var bmp = new RenderTargetBitmap((int)(dgRow.ActualWidth*dpi.DpiScaleX), (int)(dgRow.ActualHeight*dpi.DpiScaleY), dpi.PixelsPerInchX, dpi.PixelsPerInchY, PixelFormats.Pbgra32); bmp.Render(dgRow); if (bmp.CanFreeze) { bmp.Freeze(); } return bmp; }); string jpgFileName = System.IO.Path.Combine(jpgDir, $"{++jpgIdx}.jpg"); await Task.Run(() => { SaveRtbAsPictures(rtb, jpgFileName); }); } } private void SaveRtbAsPictures(BitmapSource bmpSource, string jpgFileName) { using (FileStream fileStream = new FileStream(jpgFileName, FileMode.Create)) { JpegBitmapEncoder encoder = new JpegBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bmpSource)); encoder.Save(fileStream); Debug.WriteLine($"Jpg File:{jpgFileName}"); } }
//app.cs using Microsoft.Extensions.DependencyInjection; using System.Configuration; using System.Data; using System.Windows; namespace WpfApp12 { /// <summary> /// Interaction logic for App.xaml /// </summary> public partial class App : Application { ServiceProvider serviceProvider; protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var serviceCollection = new ServiceCollection(); ConfigureServices(serviceCollection); serviceProvider = serviceCollection.BuildServiceProvider(); var mainWin = serviceProvider.GetRequiredService<MainWindow>(); mainWin.Show(); } private void ConfigureServices(ServiceCollection serviceCollection) { serviceCollection.AddSingleton<IIDService, IDService>(); serviceCollection.AddSingleton<INameService, NameService>(); serviceCollection.AddSingleton<IISBNService, ISBNService>(); serviceCollection.AddSingleton<MainWindow>(); serviceCollection.AddSingleton<MainVM>(); } protected override void OnExit(ExitEventArgs e) { base.OnExit(e); serviceProvider?.Dispose(); } } } //xaml <Window x:Class="WpfApp12.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:WpfApp12" WindowState="Maximized" mc:Ignorable="d" Title="{Binding StatusMsg,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"> <Grid> <DataGrid ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.Columns> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Grid Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource AncestorType=Window}}" Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource AncestorType=Window}}"> <Grid.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="50"/> <Setter Property="HorizontalAlignment" Value="Center"/> <Setter Property="VerticalAlignment" Value="Center"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="Red"/> <Setter Property="FontSize" Value="100"/> </Trigger> </Style.Triggers> </Style> </Grid.Resources> <Grid.Background> <ImageBrush ImageSource="{Binding ImgSource}"/> </Grid.Background> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <TextBlock Text="{Binding ID}" Grid.Column="0" Grid.Row="0"/> <TextBlock Text="{Binding Name}" Grid.Column="1" Grid.Row="0"/> <TextBlock Text="{Binding ISBN}" Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1"/> </Grid> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> <DataGrid.ContextMenu> <ContextMenu> <MenuItem Header="Save Items As Pictures" Command="{Binding SaveListBoxItemsAsPicturesCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},Path=PlacementTarget}"/> </ContextMenu> </DataGrid.ContextMenu> </DataGrid> </Grid> </Window> //xaml.cs using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Runtime.Intrinsics.X86; 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 WpfApp12 { /// <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/2; } } } } 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() { SaveListBoxItemsAsPicturesCommand=new RelayCommand<object>(async (obj) => await SaveListBoxItemsAsPicturesCommandExecuted(obj)); } private async Task SaveListBoxItemsAsPicturesCommandExecuted(object? obj) { if (obj==null) { return; } var dg = obj as DataGrid; if (dg==null) { return; } var items = dg.Items.Cast<Book>().ToList(); string jpgDir = $"JPG_{DateTime.Now.ToString("yyyyMMddHHmmsssffff")}"; if (!Directory.Exists(jpgDir)) { Directory.CreateDirectory(jpgDir); } int jpgIdx = 0; foreach (var item in items) { var dgRow = await Application.Current.Dispatcher.InvokeAsync(() => { dg.ScrollIntoView(item); dg.UpdateLayout(); var tempItem = dg.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow; return tempItem; }); if (dgRow==null) { continue; } var rtb = await Application.Current.Dispatcher.InvokeAsync(() => { var dpi = VisualTreeHelper.GetDpi(dgRow); var bmp = new RenderTargetBitmap((int)(dgRow.ActualWidth*dpi.DpiScaleX), (int)(dgRow.ActualHeight*dpi.DpiScaleY), dpi.PixelsPerInchX, dpi.PixelsPerInchY, PixelFormats.Pbgra32); bmp.Render(dgRow); if (bmp.CanFreeze) { bmp.Freeze(); } return bmp; }); string jpgFileName = System.IO.Path.Combine(jpgDir, $"{++jpgIdx}.jpg"); await Task.Run(() => { SaveRtbAsPictures(rtb, jpgFileName); }); } } private void SaveRtbAsPictures(BitmapSource bmpSource, string jpgFileName) { using (FileStream fileStream = new FileStream(jpgFileName, FileMode.Create)) { JpegBitmapEncoder encoder = new JpegBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bmpSource)); encoder.Save(fileStream); Debug.WriteLine($"Jpg File:{jpgFileName}"); } } private async Task InitData() { string imgDir = @"../../../Images"; if (!Directory.Exists(imgDir)) { return; } var imgs = Directory.GetFiles(imgDir); if (imgs==null || !imgs.Any()) { return; } int imgsCount = imgs.Count(); BooksCollection=new ObservableCollection<Book>(); List<Book> booksList = new List<Book>(); await Task.Run(async () => { for (int i = 0; i<1000000; i++) { booksList.Add(new Book() { ID=idService.GetID(), Name=nameService.GetName(), ISBN=iSBNService.GetISBN(), ImgSource=GetImgSourceViaUrl(imgs[i%imgsCount]) }); if (i%100000==0) { await PopulateBooksCollection(booksList); } } if (booksList.Count>0) { await PopulateBooksCollection(booksList); } }); } private async Task PopulateBooksCollection(List<Book> booksList) { var tempList = booksList.ToList(); booksList.Clear(); await Application.Current.Dispatcher.InvokeAsync(() => { foreach (var bk in tempList) { BooksCollection.Add(bk); } StatusMsg=$"BooksCollection.Count:{BooksCollection.Count}"; }); } private ImageSource GetImgSourceViaUrl(string imgUrl) { BitmapImage bmi = new BitmapImage(); if (!File.Exists(imgUrl)) { return bmi; } bmi.BeginInit(); bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute); bmi.EndInit(); if (bmi.CanFreeze) { bmi.Freeze(); } return bmi; } [ObservableProperty] private double gridWidth; [ObservableProperty] private double gridHeight; [ObservableProperty] private ObservableCollection<Book> booksCollection; [ObservableProperty] private string statusMsg; public RelayCommand<object> SaveListBoxItemsAsPicturesCommand { get; set; } } public class ISBNService : IISBNService { int idx = 0; public string GetISBN() { return $"ISBN_{++idx}_{Guid.NewGuid().ToString():N}"; } } public interface IISBNService { string GetISBN(); } public class NameService : INameService { int idx = 0; public string GetName() { return $"Name_{++idx}"; } } public interface INameService { string GetName(); } public interface IIDService { public int GetID(); } public class IDService : IIDService { int id = 0; public int GetID() { return ++id; } } public class Book { public int ID { get; set; } public string Name { get; set; } public string ISBN { get; set; } public ImageSource ImgSource { get; set; } } }