WPF save datagrid as jpgs row by row, withou block ui and responsive via Application.Current.Dispatcher.InvokeAsync

 

 

private ICommand exportASJPGCommand;
public ICommand ExportASJPGCommand
{
    get
    {
        if (exportASJPGCommand==null)
        {
            exportASJPGCommand=new DelCommand(ExportASJPGCommandExecutedAsync);
        }
        return exportASJPGCommand;
    }
}

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

        await SaveDataGridItemsAsJPGSAsync(dg, dir);

        MessageBox.Show($"Saved pictures in {dir}", "Saved Successfully!");
    }
}

private async Task SaveDataGridItemsAsJPGSAsync(DataGrid dg, string dir)
{
    foreach (var item in dg.Items)
    {
        var row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(item);
        if (row!=null)
        {
            var bmp = await Application.Current.Dispatcher.InvokeAsync(() => RenderRowAsBitmapSource(row));               

            var bk = row.DataContext as Book;
            if(bk!=null)
            {
                string jpgFile = System.IO.Path.Combine(dir, $"JPG_{bk.Id}.jpg");
                await Task.Run(() =>
                {
                    SaveBitmapAsJpg(bmp, jpgFile);
                });
            }
        }
    }
}

private RenderTargetBitmap RenderRowAsBitmapSource(DataGridRow row)
{
    int width = (int)row.ActualWidth * 2;
    int height = (int)row.ActualHeight * 2;

    var rtb = new RenderTargetBitmap(width, height, 192, 192, 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);

    // Freeze so it can cross threads safely
    rtb.Freeze();

    return rtb;
}

private void SaveBitmapAsJpg(BitmapSource bmp, string fileName)
{
    var encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bmp));

    using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
    {
        encoder.Save(fileStream);
    }
}

 

 

 

image

 

 

 

 

 

 

image

 

 

 

 

//xaml
<Window x:Class="WpfApp60.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:WpfApp60"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <DataGrid ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
          AutoGenerateColumns="False"
          CanUserAddRows="False"
          VirtualizingPanel.IsVirtualizing="False"
          ScrollViewer.CanContentScroll="False" >
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Grid Width="{Binding DataContext.GridWidth,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
                          Height="{Binding DataContext.GridHeight,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,
                        RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}">
                                <Grid.Background>
                                    <ImageBrush ImageSource="{Binding ImgSource}"/>
                                </Grid.Background>
                                <Grid.Resources>
                                    <Style TargetType="{x:Type TextBlock}">
                                        <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="50"/>
                                                <Setter Property="Foreground" Value="Red"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </Grid.Resources>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Text="{Binding Id}" Grid.Column="0"/>
                                <TextBlock Text="{Binding Name}" Grid.Column="1"/>
                                <TextBlock Text="{Binding Author}" Grid.Column="2"/>
                                <TextBlock Text="{Binding ISBN}" Grid.Column="3"/>
                                <TextBlock Text="{Binding Summary}" Grid.Column="4"/>
                                <TextBlock Text="{Binding Title}" Grid.Column="5"/>
                                <TextBlock Text="{Binding Topic}" Grid.Column="6"/>
                            </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 System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfApp60
{
    /// <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;
        private FrameworkElement fe;

        public MainVM(Window winValue)
        {
            win = winValue;
            if (win!=null)
            {
                win.Loaded+=Win_Loaded;
            }
        }

        private void Win_Loaded(object sender, RoutedEventArgs e)
        {
            var tempFe = win.Content as FrameworkElement;
            if (tempFe==null)
            {
                return;
            }
            fe=tempFe;
            GridWidth=fe.ActualWidth;
            GridHeight=(fe.ActualHeight-50)/2;
            InitData();
        }

        private void InitData()
        {
            var dir = @"../../../Images";
            if (!Directory.Exists(dir))
            {
                return;
            }

            var imgs = Directory.GetFiles(dir);
            if (imgs==null || !imgs.Any())
            {
                return;
            }

            int imgsCount = imgs.Count();
            BooksCollection=new ObservableCollection<Book>();

            for (int i = 0; i<100; i++)
            {
                BooksCollection.Add(new Book()
                {
                    Id=i+1,
                    Name=$"Name_{i+1}",
                    Author=$"Author_{i+1}",
                    ISBN=$"{Guid.NewGuid().ToString("N")}_{i+1}",
                    Summary=$"Summary_{i+1}",
                    Title=$"Title_{i+1}",
                    Topic=$"Topic_{i+1}",
                    ImgSource=GetImgSourceViaUrl(imgs[i%imgsCount]),
                });
            }
        }

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

        private ICommand exportASJPGCommand;
        public ICommand ExportASJPGCommand
        {
            get
            {
                if (exportASJPGCommand==null)
                {
                    exportASJPGCommand=new DelCommand(ExportASJPGCommandExecutedAsync);
                }
                return exportASJPGCommand;
            }
        }

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

                await SaveDataGridItemsAsJPGSAsync(dg, dir);

                MessageBox.Show($"Saved pictures in {dir}", "Saved Successfully!");
            }
        }

        private async Task SaveDataGridItemsAsJPGSAsync(DataGrid dg, string dir)
        {
            foreach (var item in dg.Items)
            {
                var row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromItem(item);
                if (row!=null)
                {
                    var bmp = await Application.Current.Dispatcher.InvokeAsync(() => RenderRowAsBitmapSource(row));               

                    var bk = row.DataContext as Book;
                    if(bk!=null)
                    {
                        string jpgFile = System.IO.Path.Combine(dir, $"JPG_{bk.Id}.jpg");
                        await Task.Run(() =>
                        {
                            SaveBitmapAsJpg(bmp, jpgFile);
                        });
                    }
                }
            }
        }

        private RenderTargetBitmap RenderRowAsBitmapSource(DataGridRow row)
        {
            int width = (int)row.ActualWidth * 2;
            int height = (int)row.ActualHeight * 2;

            var rtb = new RenderTargetBitmap(width, height, 192, 192, 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);

            // Freeze so it can cross threads safely
            rtb.Freeze();

            return rtb;
        }

        private void SaveBitmapAsJpg(BitmapSource bmp, string fileName)
        {
            var encoder = new JpegBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(bmp));

            using (FileStream fileStream = new FileStream(fileName, FileMode.Create))
            {
                encoder.Save(fileStream);
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            if (handler!=null)
            {
                handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                if (value!=booksCollection)
                {
                    booksCollection = value;
                    OnPropertyChanged();
                }
            }
        }

        private double gridWidth;
        public double GridWidth
        {
            get
            {
                return gridWidth;
            }
            set
            {
                if (value!=gridWidth)
                {
                    gridWidth=value;
                    OnPropertyChanged();
                }
            }
        }

        private double gridHeight;
        public double GridHeight
        {
            get
            {
                return gridHeight;
            }
            set
            {
                if (value!= gridHeight)
                {
                    gridHeight=value;
                    OnPropertyChanged();
                }
            }
        }

        private double columnWidth;
        public double ColumnWidth
        {
            get
            {
                return columnWidth;
            }
            set
            {
                if (value!=columnWidth)
                {
                    columnWidth=value;
                    OnPropertyChanged();
                }
            }
        }
    }

    public class Book
    {
        public int Id { get; set; }
        public ImageSource ImgSource { get; set; }
        public string Name { get; set; }
        public string Author { get; set; }
        public string ISBN { get; set; }
        public string Summary { get; set; }
        public string Title { get; set; }
        public string Topic { 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);
        }
    }
}

 

posted @ 2025-08-27 11:34  FredGrit  阅读(6)  评论(0)    收藏  举报