WPF DataGrid SelectionMode="Extended" and ContextMenu MenuItem command pass parameters as PlacementTarget.SelectedItems and convert SelectedItemsCollection to List

<DataGrid.ContextMenu>
    <ContextMenu>
        <MenuItem Header="Export Selected"
                  Command="{Binding ExportSelectedCommand}"
                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu},
            Path=PlacementTarget.SelectedItems}"/>
    </ContextMenu>
</DataGrid.ContextMenu>

        private void ExportSelectedCommandExecuted(object? obj)
        {
            var items=(System.Collections.IList)obj;
            if(items!=null)
            {
                var itemsList = items.Cast<Book>()?.ToList();
                if (itemsList != null && itemsList.Any())
                {
                    string jsonStr = JsonConvert.SerializeObject(itemsList, Formatting.Indented);
                    string jsonFile = $"Json_Selected_{DateTime.Now.ToString("yyyyMMddHHmssffff")}.json";
                    using (StreamWriter jsonWriter = new StreamWriter(jsonFile, false, Encoding.UTF8))
                    {
                        jsonWriter.WriteLine(jsonStr);
                        MessageBox.Show($"Export {itemsList.Count()} items to json file {jsonFile}", DateTime.Now.ToString("yyyyMMddHHmssffff"));
                    }
                }
            }
        }

 

 

 

image

 

image

 

 

 

image

 

 

<Window x:Class="WpfApp2.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:WpfApp2"
        mc:Ignorable="d"
        WindowState="Maximized"
        Title="{Binding MainTitle}" Height="450" Width="800">
    <Window.Resources>
        <local:ImgUrlToImgSourceConverter x:Key="ImgUrlToImgSourceConverter"/>
    </Window.Resources>
    <Window.DataContext>
        <local:MainVM/>
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding BooksCollection}"
                  VirtualizingPanel.IsVirtualizing="True"
                  VirtualizingPanel.VirtualizationMode="Recycling"
                  VirtualizingPanel.CacheLength="2,2"
                  VirtualizingPanel.CacheLengthUnit="Item"
                  AutoGenerateColumns="False"
                  CanUserAddRows="False"
                  ScrollViewer.CanContentScroll="True"
                  ScrollViewer.IsDeferredScrollingEnabled="True"
                  SelectionMode="Extended">
            <DataGrid.Columns>
                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Grid Width="{Binding DataContext.GridWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}"
                                  Height="{Binding DataContext.GridHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}}}">
                                <Grid.Resources>
                                    <Style TargetType="TextBlock">
                                        <Setter Property="FontSize" Value="30"/>
                                        <Style.Triggers>
                                            <Trigger Property="IsMouseOver" Value="True">
                                                <Setter Property="FontSize" Value="50"/>
                                                <Setter Property="Foreground" Value="Red"/>
                                            </Trigger>
                                        </Style.Triggers>
                                    </Style>
                                </Grid.Resources>
                                <Grid.Background>
                                    <ImageBrush ImageSource="{Binding ImgUrl,Converter={StaticResource ImgUrlToImgSourceConverter}}"/>
                                </Grid.Background>
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition/>
                                    <ColumnDefinition/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Row="0" Grid.Column="0" Text="{Binding Id}"/>
                                <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}"/>
                                <TextBlock Grid.Row="1" Grid.Column="0" Text="{Binding Author}"/>
                                <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding Comment}"/>
                                <TextBlock Grid.Row="2" Grid.Column="0" Text="{Binding Content}"/>
                                <TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Title}"/>
                                <TextBlock Grid.Row="3" Grid.Column="0" Text="{Binding Topic}"/>
                                <TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Summary}"/>
                                <TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Text="{Binding ISBN}"/>
                            </Grid>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Export All"
                              Command="{Binding ExportAllCommand}"
                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu},
                        Path=PlacementTarget}"/>
                    <MenuItem Header="Export Selected"
                              Command="{Binding ExportSelectedCommand}"
                              CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ContextMenu},
                        Path=PlacementTarget.SelectedItems}"/>
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
    </Grid>
</Window>





using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Printing;
using System.Runtime.CompilerServices;
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 Newtonsoft.Json;
using System.Collections;

namespace WpfApp2
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.Loaded += async (s, e) =>
            {
                var vm = this.DataContext as MainVM;
                if (vm != null)
                {
                    var fe = this.Content as FrameworkElement;
                    if (fe != null)
                    {
                        vm.GridWidth = fe.ActualWidth;
                        vm.GridHeight = fe.ActualHeight / 2;
                        await vm.InitBooksCollectionAsync(1000000);
                    }
                }
            };
        }
    }

    public class MainVM : INotifyPropertyChanged
    {
        public MainVM()
        {
            ExportAllCommand = new DelCommand(ExportAllCommandExecuted);
            ExportSelectedCommand = new DelCommand(ExportSelectedCommandExecuted);
        }

        private void ExportSelectedCommandExecuted(object? obj)
        {
            var items=(System.Collections.IList)obj;
            if(items!=null)
            {
                var itemsList = items.Cast<Book>()?.ToList();
                if (itemsList != null && itemsList.Any())
                {
                    string jsonStr = JsonConvert.SerializeObject(itemsList, Formatting.Indented);
                    string jsonFile = $"Json_Selected_{DateTime.Now.ToString("yyyyMMddHHmssffff")}.json";
                    using (StreamWriter jsonWriter = new StreamWriter(jsonFile, false, Encoding.UTF8))
                    {
                        jsonWriter.WriteLine(jsonStr);
                        MessageBox.Show($"Export {itemsList.Count()} items to json file {jsonFile}", DateTime.Now.ToString("yyyyMMddHHmssffff"));
                    }
                }
            }
        }

        private void ExportAllCommandExecuted(object? obj)
        {
            var dg = obj as DataGrid;
            if (dg != null)
            {
                var itemsList = dg.Items.Cast<Book>()?.ToList();
                if(itemsList!=null && itemsList.Any())
                {
                    string jsonStr=JsonConvert.SerializeObject(itemsList, Formatting.Indented);
                    string jsonFile = $"Json_All_{DateTime.Now.ToString("yyyyMMddHHmssffff")}.json";
                    using (StreamWriter jsonWriter = new StreamWriter(jsonFile, false, Encoding.UTF8))
                    {
                        jsonWriter.WriteLine(jsonStr);
                        MessageBox.Show($"Export {itemsList.Count()} items to json file {jsonFile}", DateTime.Now.ToString("yyyyMMddHHmssffff"));
                    }
                }
            }
        }

        public async Task InitBooksCollectionAsync(int cnt = 10000001)
        {
            var imgDir = @"../../../Images";
            if (!Directory.Exists(imgDir))
            {
                return;
            }
            var imgs = Directory.GetFiles(imgDir);
            if (imgs == null || !imgs.Any())
            {
                return;
            }

            int imgsCnt = imgs.Count();
            BooksCollection = new ObservableCollection<Book>();
            List<Book> booksList = new List<Book>();
            for (int i = 1; i < cnt+1; i++)
            {
                booksList.Add(new Book()
                {
                    Id = i,
                    Name = $"Name_{i}",
                    ISBN = $"ISBN_{i}_{Guid.NewGuid():N}",
                    Author = $"Author_{i}",
                    Comment = $"Comment_{i}",
                    Content = $"Content_{i}",
                    ImgUrl = $"{imgs[i % imgsCnt]}",
                    Summary = $"Summary_{i}",
                    Title = $"Title_{i}",
                    Topic = $"Topic_{i}"
                });

                if (i % 100000 == 0)
                {
                    await PopulateBooksCollectionAsync(booksList);
                }
            }

            if (booksList.Any())
            {
                await PopulateBooksCollectionAsync(booksList);
            }
        }

        private async Task PopulateBooksCollectionAsync(List<Book> booksList)
        {
            var tempList = booksList.ToList();
            booksList.Clear();
            await Application.Current.Dispatcher.InvokeAsync(() =>
            {
                foreach (Book book in tempList)
                {
                    BooksCollection.Add(book);
                }
                MainTitle = $"{DateTime.Now},loaded {BooksCollection.Count} items,{GetMemory()}";
            });
        }

        public ICommand ExportAllCommand { get; set;}
        public ICommand ExportSelectedCommand { get; set;}

        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 ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }
            set
            {
                if (value != booksCollection)
                {
                    booksCollection = value;
                    OnPropertyChanged(nameof(BooksCollection));
                }
            }
        }

        private string mainTitle;
        public string MainTitle
        {
            get
            {
                return mainTitle;
            }
            set
            {
                if (value != mainTitle)
                {
                    mainTitle = value;
                    OnPropertyChanged();
                }
            }
        }

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

        private string GetMemory()
        {
            var proc = Process.GetCurrentProcess();
            return $"memory {proc.PrivateMemorySize64 / 1024 / 1024:N2} M";
        }
    }

    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 Name { get; set; }
        public string ISBN { get; set; }
        public string Author { get; set; }
        public string Comment { get; set; }
        public string Content { get; set; }
        public string ImgUrl { get; set; }
        public string Summary { get; set; }
        public string Title { get; set; }
        public string Topic { get; set; }
    }

    public class ImgUrlToImgSourceConverter : IValueConverter
    {
        Dictionary<string, ImageSource> imgDicCache = new Dictionary<string, ImageSource>();
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                string? imgUrl = value?.ToString();
                if (!string.IsNullOrEmpty(imgUrl))
                {
                    if (imgDicCache.TryGetValue(imgUrl, out ImageSource? imgSource))
                    {
                        return imgSource;
                    }
                    BitmapImage bmi = new BitmapImage();
                    bmi.BeginInit();
                    bmi.CreateOptions = BitmapCreateOptions.DelayCreation;
                    bmi.UriSource = new Uri(imgUrl, UriKind.RelativeOrAbsolute);
                    bmi.EndInit();
                    if (bmi.CanFreeze)
                    {
                        bmi.Freeze();
                    }
                    imgDicCache[imgUrl] = bmi;
                    return bmi;
                }
                return null;
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

 

posted @ 2026-02-08 10:55  FredGrit  阅读(5)  评论(0)    收藏  举报