WPF DataGrid ContextMenu MenuItem MVVM save all datagrid rows as jpg file via RenderTargetBitmap and JpegBitmapEncoder, Task.Yield() avoid UI blocking issues

Install-Package CommunityToolkit.Mvvm;

 

 

//xaml
<Window x:Class="WpfApp69.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"
        WindowState="Maximized"
        xmlns:local="clr-namespace:WpfApp69"
        mc:Ignorable="d"
        Title="{Binding JPGFile,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
        Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                  VirtualizingPanel.IsVirtualizing="False"
                  VirtualizingPanel.IsContainerVirtualizable="False"
                  AutoGenerateColumns="False"
                  CanUserAddRows="False">
            <DataGrid.ItemContainerStyle>
                <Style TargetType="{x:Type DataGridRow}">
                    <Setter Property="Height" Value="{Binding DataContext.RowHeight,
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
                    <Setter Property="Width" Value="{Binding DataContext.RowWidth,
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"/>
                    <Setter Property="FontSize" Value="30"/>
                    <Setter Property="VerticalAlignment" Value="Center"/>
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="FontSize" Value="50"/>
                            <Setter Property="Foreground" Value="Red"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.ItemContainerStyle>
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Grid  Width="{Binding DataContext.RowWidth,
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}">
                                <Grid.Background>
                                    <ImageBrush ImageSource="{Binding ImgSource}"/>
                                </Grid.Background>
                                <Grid.Resources>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <Setter Property="VerticalAlignment" Value="Center"/>
                                    </Style>
                                </Grid.Resources>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{Binding Id}" Grid.Column="0" VerticalAlignment="Center" />
                                <TextBlock Text="{Binding Author}"  Grid.Column="1"/>
                                <TextBlock Text="{Binding Name}" Grid.Column="2" />
                                <TextBlock Text="{Binding Chapter}" Grid.Column="3" />
                                <TextBlock Text="{Binding Summary}" Grid.Column="4" />
                                <TextBlock Text="{Binding Title}" Grid.Column="5" />
                                <TextBlock Text="{Binding Topic}"  Grid.Column="6"/>
                                <TextBlock Text="{Binding Press}" Grid.Column="7" />
                                <TextBlock Text="{Binding ISBN}" Grid.Column="8" />
                            </Grid>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Export As Jpg"
                              Command="{Binding ExportAsJpgCommand}"
                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ContextMenu}},Path=PlacementTarget}"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</Window>


//cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
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 Path = System.IO.Path;

namespace WpfApp69
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            var vm = new MainVM(this);
            this.DataContext=vm;
        }
    }

    public partial class MainVM : ObservableObject
    {
        Window win;
        public ICommand ExportAsJpgCommand { get; set; }
        public MainVM(Window winValue)
        {
            win= winValue;
            if (win!=null)
            {
                win.SizeChanged+=Win_SizeChanged;
            }

            InitBooksCollection();
            InitCommands();
        }

        private void InitCommands()
        {
            ExportAsJpgCommand=new DelCommand(ExportAsJpgCommandExecuted);
        }

        private async void ExportAsJpgCommandExecuted(object? obj)
        {
            var dg = obj as DataGrid;
            if (dg!=null && dg.Items!=null && dg.Items.Count>0)
            {
                string dir = $"Row_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}";
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }
                int idx = 0;

                foreach (var item in dg.Items)
                {
                    var itemFe = dg.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
                    if (itemFe!=null)
                    {
                        itemFe.UpdateLayout();
                        string rowJpg = Path.Combine(dir, $"Row_{++idx}.jpg");
                        JPGFile=rowJpg;
                        // Render synchronously on UI thread
                        RenderTargetBitmap bmpSource = RenderItemAsBmpSource(itemFe);

                        // Save in background
                        await Task.Run(() =>
                        {
                            SaveBmpSourceAsJpgFile(bmpSource, rowJpg);
                        });

                        //Yield to UI → prevents freezing during long loops
                        await Task.Yield();
                    }
                }               
                MessageBox.Show($"Finished export datagridrow as pictures in {dir}");
            }
        } 

        private void SaveBmpSourceAsJpgFile(RenderTargetBitmap bmpSource, string rowJpg)
        {
            using (FileStream fileStream = new FileStream(rowJpg, FileMode.Create))
            {
                JpegBitmapEncoder jpgEncoder = new JpegBitmapEncoder();
                jpgEncoder.Frames.Add(BitmapFrame.Create(bmpSource));
                jpgEncoder.Save(fileStream);
                System.Diagnostics.Debug.WriteLine(rowJpg);
            }
        }

        private RenderTargetBitmap RenderItemAsBmpSource(FrameworkElement fe)
        {
            var rtb = new RenderTargetBitmap((int)fe.ActualWidth*2,
                (int)fe.ActualHeight*2,
                96*2,
                96*2,
                PixelFormats.Pbgra32);

            DrawingVisual drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                VisualBrush visualBrush = new VisualBrush(fe);
                drawingContext.DrawRectangle(visualBrush, null, new Rect(0, 0, fe.ActualWidth, fe.ActualHeight));
            }

            rtb.Render(drawingVisual);
            if(rtb.CanFreeze)
            {
                rtb.Freeze();
            }
            return rtb;
        }

        private void InitBooksCollection()
        {
            Task.Run(() =>
            {
                string dir = @"../../../Images";
                if (!Directory.Exists(dir))
                {
                    return;
                }
                var imgs = Directory.GetFiles(dir);
                int imgsCount = imgs.Count();
                var booksList = new List<Book>();
                for (int i = 1; i<=1000; i++)
                {
                    booksList.Add(new Book()
                    {
                        Id=i,
                        Name=$"Name_{i}",
                        Author=$"Author_{i}",
                        Chapter=$"Chapter_{i}",
                        Summary=$"Summary_{i}",
                        Title=$"Title_{i}",
                        Topic=$"Topic_{i}",
                        Press=$"Press_{i}",
                        ISBN=$"ISBN_{i}",
                        ImgSource=GetImgSourceFromImgUrl(imgs[i%imgsCount])
                    });
                }

                BooksCollection=new ObservableCollection<Book>(booksList);
            });
        }

        private ImageSource GetImgSourceFromImgUrl(string imgUrl)
        {
            BitmapImage bmi = new BitmapImage();
            bmi.BeginInit();
            bmi.UriSource=new Uri(imgUrl, UriKind.RelativeOrAbsolute);
            bmi.EndInit();
            if (bmi.CanFreeze)
            {
                bmi.Freeze();
            }
            return bmi;
        }

        private void Win_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var fe = win.Content as FrameworkElement;
            if (fe!=null)
            {
                RowHeight=fe.ActualHeight/2;
                RowWidth=fe.ActualWidth;
            }
        }

        [ObservableProperty]
        private ObservableCollection<Book> booksCollection;

        [ObservableProperty]
        private double rowHeight;

        [ObservableProperty]
        private double rowWidth;

        [ObservableProperty]
        private string jPGFile;
    }

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

    public class Book
    {
        public int Id { get; set; }
        public string Author { get; set; }
        public string Name { get; set; }
        public string Chapter { get; set; }
        public string Summary { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
        public string Press { get; set; }
        public string ISBN { get; set; }
        public ImageSource ImgSource { get; set; }
    }
}

 

 

 

image

 

posted @ 2025-09-05 23:21  FredGrit  阅读(8)  评论(0)    收藏  举报