























Install-Package Microsoft.Xaml.Behaviors.Wpf
Install-Package Newtonsoft.Json
public class DatagridBehavior : Behavior<DataGrid> { ContextMenu ctxMenu; MenuItem saveJsonMenuItem; MenuItem deleteMenuItem; public ICommand SaveJsonCommand { get { return (ICommand)GetValue(SaveJsonCommandProperty); } set { SetValue(SaveJsonCommandProperty, value); } } // Using a DependencyProperty as the backing store for SaveJsonCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty SaveJsonCommandProperty = DependencyProperty.Register(nameof(SaveJsonCommand), typeof(ICommand), typeof(DatagridBehavior), new PropertyMetadata(null)); public ICommand DeleteCommand { get { return (ICommand)GetValue(DeleteCommandProperty); } set { SetValue(DeleteCommandProperty, value); } } // Using a DependencyProperty as the backing store for DeleteCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register(nameof(DeleteCommand), typeof(ICommand), typeof(DatagridBehavior), new PropertyMetadata(null)); public object CmdPara { get { return (object)GetValue(CmdParaProperty); } set { SetValue(CmdParaProperty, value); } } // Using a DependencyProperty as the backing store for CmdPara. This enables animation, styling, binding, etc... public static readonly DependencyProperty CmdParaProperty = DependencyProperty.Register(nameof(CmdPara), typeof(object), typeof(DatagridBehavior), new PropertyMetadata(null)); public DatagridBehavior() { } protected override void OnAttached() { CreateContextMenu(); AssociatedObject.ContextMenu = ctxMenu; base.OnAttached(); } private void CreateContextMenu() { ctxMenu = new ContextMenu(); saveJsonMenuItem = new MenuItem() { Header = "Save Json" }; saveJsonMenuItem.Click += SaveJsonMenuItem_Click; ctxMenu.Items.Add(saveJsonMenuItem); deleteMenuItem = new MenuItem() { Header = "Delete Item" }; deleteMenuItem.Click += DeleteMenuItem_Click; ctxMenu.Items.Add(deleteMenuItem); } private void DeleteMenuItem_Click(object sender, RoutedEventArgs e) { DeleteCommand?.Execute(CmdPara); } private void SaveJsonMenuItem_Click(object sender, RoutedEventArgs e) { SaveJsonCommand?.Execute(CmdPara); } protected override void OnDetaching() { base.OnDetaching(); } } <behavior:Interaction.Behaviors> <local:DatagridBehavior SaveJsonCommand="{Binding Path=DataContext.SaveCmd,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" DeleteCommand="{Binding Path=DataContext.DeleteCmd,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CmdPara="{Binding Path=SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/> </behavior:Interaction.Behaviors> public ICommand SelectedCmd { get; set; } public ICommand SaveCmd { get; set; } public ICommand DeleteCmd { get; set; } private void InitCmds() { SelectedCmd = new DelCommand(SelectedCmdExecuted); SaveCmd = new DelCommand(SaveCmdExecuted); DeleteCmd = new DelCommand(DeleteCmdExecuted); } private void DeleteCmdExecuted(object? obj) { var items = ((System.Collections.IList)obj).Cast<Book>()?.ToList(); if(items!=null && items.Any()) { foreach(var item in items) { if(BooksCollection.Remove(item)) { System.Diagnostics.Debug.WriteLine($"{DateTime.Now}: removed {item}"); } } } } private void SaveCmdExecuted(object? obj) { var items = ((System.Collections.IList)obj).Cast<Book>()?.ToList(); if(items!=null && items.Any()) { string jsonStr = JsonConvert.SerializeObject(items, Formatting.Indented); string jsonFile = $"Json_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.json"; using (StreamWriter jsonWriter = new StreamWriter(jsonFile,false, Encoding.UTF8)) { jsonWriter.WriteLine(jsonStr); System.Diagnostics.Debug.WriteLine($"{DateTime.Now},write json to {jsonFile}"); } } }
2026-05-19 18:42:00,write json to Json_202605191842003493.json Selected count:14,First Id:1,Last Id:14 Selected count:1,First Id:11,Last Id:11 Selected count:5,First Id:11,Last Id:15 Selected count:5,First Id:11,Last Id:15 2026-05-19 18:42:09: removed Id:11,Name:Name_11,ISBN:ISBN_11_12352a8065fd439bbd32d45fceaabbc4 2026-05-19 18:42:09: removed Id:12,Name:Name_12,ISBN:ISBN_12_e0859de5557844679935fd870e3ad33f 2026-05-19 18:42:09: removed Id:13,Name:Name_13,ISBN:ISBN_13_a21f1e5c264d43e5bda089d8ecc040ce 2026-05-19 18:42:09: removed Id:14,Name:Name_14,ISBN:ISBN_14_dd7476cee61740fba7190b022fb84cb7 2026-05-19 18:42:09: removed Id:15,Name:Name_15,ISBN:ISBN_15_3cd20b8d7e27447a8901b3e2ece17439

<Window x:Class="WpfApp31.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:WpfApp31" xmlns:behavior="http://schemas.microsoft.com/xaml/behaviors" mc:Ignorable="d" Title="{Binding MainTitle}" WindowState="Maximized"> <Window.DataContext> <local:MainVM/> </Window.DataContext> <Grid> <DataGrid ItemsSource="{Binding BooksCollection}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLengthUnit="Item" VirtualizingPanel.CacheLength="2,2" ScrollViewer.CanContentScroll="True" ScrollViewer.IsDeferredScrollingEnabled="True" UseLayoutRounding="True" SnapsToDevicePixels="True" EnableColumnVirtualization="True" EnableRowVirtualization="True" AutoGenerateColumns="True" CanUserAddRows="False" SelectionMode="Extended" IsReadOnly="True"> <DataGrid.Resources> <Style TargetType="DataGridRow"> <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> </DataGrid.Resources> <behavior:Interaction.Triggers> <behavior:EventTrigger EventName="MouseUp"> <behavior:InvokeCommandAction Command="{Binding SelectedCmd}" PassEventArgsToCommand="True" CommandParameter="{Binding Path=SelectedItems,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"> </behavior:InvokeCommandAction> </behavior:EventTrigger> <behavior:EventTrigger EventName="MouseDoubleClick"> <behavior:CallMethodAction MethodName="DataGrid_MouseDoubleClick" TargetObject="{Binding}"/> </behavior:EventTrigger> </behavior:Interaction.Triggers> <behavior:Interaction.Behaviors> <local:DatagridBehavior SaveJsonCommand="{Binding Path=DataContext.SaveCmd,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" DeleteCommand="{Binding Path=DataContext.DeleteCmd,RelativeSource={RelativeSource AncestorType={x:Type Window}}}" CmdPara="{Binding Path=SelectedItems, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/> </behavior:Interaction.Behaviors> </DataGrid> </Grid> </Window> using Microsoft.Xaml.Behaviors; using Newtonsoft.Json; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics.Eventing.Reader; using System.IO; 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 System.Windows.Threading; namespace WpfApp31 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class MainVM : INotifyPropertyChanged { private DispatcherTimer tmr; private static long idx = 0; public ICommand SelectedCmd { get; set; } public ICommand SaveCmd { get; set; } public ICommand DeleteCmd { get; set; } public MainVM() { if (!DesignerProperties.GetIsInDesignMode(new DependencyObject())) { Task.Run(async () => { await InitBooksCollectionAsync(); }); } InitCmds(); } private void InitCmds() { SelectedCmd = new DelCommand(SelectedCmdExecuted); SaveCmd = new DelCommand(SaveCmdExecuted); DeleteCmd = new DelCommand(DeleteCmdExecuted); } private void DeleteCmdExecuted(object? obj) { var items = ((System.Collections.IList)obj).Cast<Book>()?.ToList(); if(items!=null && items.Any()) { foreach(var item in items) { if(BooksCollection.Remove(item)) { System.Diagnostics.Debug.WriteLine($"{DateTime.Now}: removed {item}"); } } } } private void SaveCmdExecuted(object? obj) { var items = ((System.Collections.IList)obj).Cast<Book>()?.ToList(); if(items!=null && items.Any()) { string jsonStr = JsonConvert.SerializeObject(items, Formatting.Indented); string jsonFile = $"Json_{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.json"; using (StreamWriter jsonWriter = new StreamWriter(jsonFile,false, Encoding.UTF8)) { jsonWriter.WriteLine(jsonStr); System.Diagnostics.Debug.WriteLine($"{DateTime.Now},write json to {jsonFile}"); } } } public void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) { var dg = sender as DataGrid; if (dg != null) { var items = dg.SelectedItems.Cast<Book>().ToList(); var msg = $"Selected {items.Count} items.FirstId:{items.FirstOrDefault()?.Id},LastId:{items.LastOrDefault()?.Id}"; System.Diagnostics.Debug.WriteLine(msg); } } private void SelectedCmdExecuted(object? obj) { var selectedItems = ((System.Collections.IList)obj)?.Cast<Book>(); var msg = $"Selected count:{selectedItems.Count()},First Id:{selectedItems.FirstOrDefault()?.Id},Last Id:{selectedItems.LastOrDefault()?.Id}"; System.Diagnostics.Debug.WriteLine(msg); } private static long GetIncrementIdx() { return Interlocked.Increment(ref idx); } private async Task InitBooksCollectionAsync(int cnt = 1000000) { BooksCollection = new ObservableCollection<Book>(); List<Book> booksList = new List<Book>(); for (int i = 0; i < cnt; i++) { var a = GetIncrementIdx(); booksList.Add(new Book() { Id = a, Name = $"Name_{a}", Author = $"Author_{a}", Abstract = $"Abstract_{a}", ISBN = $"ISBN_{a}_{Guid.NewGuid():N}", Comment = $"Comment_{a}", Content = $"Content_{a}", Summary = $"Summary_{a}", Title = $"Title_{a}", Topic = $"Topic_{a}" }); if (i % 100000 == 0) { await PopulateAsync(booksList); } } if (booksList.Any()) { await PopulateAsync(booksList); } } private async Task PopulateAsync(List<Book> booksList) { var tempList = booksList.ToList(); booksList.Clear(); await Application.Current.Dispatcher.InvokeAsync(() => { foreach (var bk in tempList) { BooksCollection.Add(bk); } MainTitle = $"{DateTime.Now},loaded {BooksCollection.Count} items"; }, DispatcherPriority.Background); } private IList<Book> selectedBooks; public IList<Book> SelectedBooks { get { return selectedBooks; } set { if (value != selectedBooks) { selectedBooks = value; OnPropertyChanged(); } } } private string mainTitle = $"{DateTime.Now},loading..."; public string MainTitle { get { return mainTitle; } set { if (value != mainTitle) { mainTitle = value; OnPropertyChanged(); } } } private ObservableCollection<Book> booksCollection; public ObservableCollection<Book> BooksCollection { get { return booksCollection; } set { if (value != booksCollection) { booksCollection = value; OnPropertyChanged(); } } } public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged([CallerMemberName] string propName = "") { var handler = PropertyChanged; handler?.Invoke(this, new PropertyChangedEventArgs(propName)); } } public class DelCommand : ICommand { private Action<object?>? execute; private Predicate<object?>? canExecute; public DelCommand(Action<object?>? executeValue, Predicate<object?>? canExecuteValue = null) { execute = executeValue ?? throw new ArgumentNullException(nameof(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?.Invoke(parameter); } } public class Book { public long Id { get; set; } public string Name { get; set; } public string Author { get; set; } public string Abstract { get; set; } public string ISBN { get; set; } public string Comment { get; set; } public string Content { get; set; } public string Summary { get; set; } public string Title { get; set; } public string Topic { get; set; } public override string ToString() { return $"Id:{Id},Name:{Name},ISBN:{ISBN}"; } } public class DatagridBehavior : Behavior<DataGrid> { ContextMenu ctxMenu; MenuItem saveJsonMenuItem; MenuItem deleteMenuItem; public ICommand SaveJsonCommand { get { return (ICommand)GetValue(SaveJsonCommandProperty); } set { SetValue(SaveJsonCommandProperty, value); } } // Using a DependencyProperty as the backing store for SaveJsonCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty SaveJsonCommandProperty = DependencyProperty.Register(nameof(SaveJsonCommand), typeof(ICommand), typeof(DatagridBehavior), new PropertyMetadata(null)); public ICommand DeleteCommand { get { return (ICommand)GetValue(DeleteCommandProperty); } set { SetValue(DeleteCommandProperty, value); } } // Using a DependencyProperty as the backing store for DeleteCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register(nameof(DeleteCommand), typeof(ICommand), typeof(DatagridBehavior), new PropertyMetadata(null)); public object CmdPara { get { return (object)GetValue(CmdParaProperty); } set { SetValue(CmdParaProperty, value); } } // Using a DependencyProperty as the backing store for CmdPara. This enables animation, styling, binding, etc... public static readonly DependencyProperty CmdParaProperty = DependencyProperty.Register(nameof(CmdPara), typeof(object), typeof(DatagridBehavior), new PropertyMetadata(null)); public DatagridBehavior() { } protected override void OnAttached() { CreateContextMenu(); AssociatedObject.ContextMenu = ctxMenu; base.OnAttached(); } private void CreateContextMenu() { ctxMenu = new ContextMenu(); saveJsonMenuItem = new MenuItem() { Header = "Save Json" }; saveJsonMenuItem.Click += SaveJsonMenuItem_Click; ctxMenu.Items.Add(saveJsonMenuItem); deleteMenuItem = new MenuItem() { Header = "Delete Item" }; deleteMenuItem.Click += DeleteMenuItem_Click; ctxMenu.Items.Add(deleteMenuItem); } private void DeleteMenuItem_Click(object sender, RoutedEventArgs e) { DeleteCommand?.Execute(CmdPara); } private void SaveJsonMenuItem_Click(object sender, RoutedEventArgs e) { SaveJsonCommand?.Execute(CmdPara); } protected override void OnDetaching() { base.OnDetaching(); } } }

2026-05-19 18:50:27: removed Id:392835,Name:Name_392835,ISBN:ISBN_392835_98844b65f6164770b986a011953f094f 2026-05-19 18:50:27: removed Id:392836,Name:Name_392836,ISBN:ISBN_392836_6e4ae22266c649979abae5e2f2aaa596 2026-05-19 18:50:27: removed Id:392837,Name:Name_392837,ISBN:ISBN_392837_0e577274c5704ffca4b482c1f0f19182 2026-05-19 18:50:27: removed Id:392838,Name:Name_392838,ISBN:ISBN_392838_9164c28643c44982b223c922960c6e7d 2026-05-19 18:50:27: removed Id:392839,Name:Name_392839,ISBN:ISBN_392839_b962b2fba23d44debffe3741cd349c33 2026-05-19 18:50:27: removed Id:392840,Name:Name_392840,ISBN:ISBN_392840_93ffaf1bea4a4f24a029ae8631efb465 2026-05-19 18:50:27: removed Id:392841,Name:Name_392841,ISBN:ISBN_392841_2a600c9a5f8d4f2b8dd1055039a033dc

此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。