





















Install-Package Microsoft.Xaml.Behaviors.WPF
public class GridBehavior : Behavior<Grid> { private ContextMenu contextMenu; private MenuItem saveItem; public GridBehavior() { } private void Mi_Click(object sender, RoutedEventArgs e) { RightButtonSaveCommand?.Execute(CmdPara); } protected override void OnAttached() { CreateContextMenu(); base.OnAttached(); AssociatedObject.ContextMenu = contextMenu; } private void CreateContextMenu() { contextMenu = new ContextMenu(); saveItem = new MenuItem(); saveItem.Header = "Save"; saveItem.Click += Mi_Click; contextMenu.Items.Add(saveItem); } protected override void OnDetaching() { base.OnDetaching(); if(saveItem!=null) { saveItem.Click -= Mi_Click; saveItem = null; } if(contextMenu!=null) { contextMenu.Items.Clear(); contextMenu = null; } AssociatedObject.ContextMenu = null; } public ICommand RightButtonSaveCommand { get { return (ICommand)GetValue(RightButtonSaveCommandProperty); } set { SetValue(RightButtonSaveCommandProperty, value); } } // Using a DependencyProperty as the backing store for RightButtonSaveCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty RightButtonSaveCommandProperty = DependencyProperty.Register(nameof(RightButtonSaveCommand), typeof(ICommand), typeof(GridBehavior), 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(GridBehavior), new PropertyMetadata(null)); }
<Window x:Class="WpfApp16.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:behavior="http://schemas.microsoft.com/xaml/behaviors" xmlns:local="clr-namespace:WpfApp16" mc:Ignorable="d" WindowState="Maximized" Title="{Binding MainTitle}"> <Window.DataContext> <local:MainVM/> </Window.DataContext> <Window.Resources> <local:ImgUrlConverter x:Key="ImgUrlConverter"/> </Window.Resources> <Grid> <ItemsControl ItemsSource="{Binding BooksCollection}"> <ItemsControl.Template> <ControlTemplate> <ScrollViewer CanContentScroll="True"> <VirtualizingStackPanel IsItemsHost="True" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.CacheLength="3,3" VirtualizingPanel.CacheLengthUnit="Item" UseLayoutRounding="True" SnapsToDevicePixels="True" ScrollViewer.IsDeferredScrollingEnabled="True" ScrollViewer.CanContentScroll="True"/> </ScrollViewer> </ControlTemplate> </ItemsControl.Template> <ItemsControl.ItemTemplate> <DataTemplate> <Grid Width="{x:Static SystemParameters.FullPrimaryScreenWidth}" Height="{x:Static SystemParameters.FullPrimaryScreenHeight}"> <Grid.Resources> <Style TargetType="TextBlock"> <Setter Property="FontSize" Value="50"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="FontSize" Value="100"/> <Setter Property="Foreground" Value="Red"/> </Trigger> </Style.Triggers> </Style> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Image Source="{Binding ImgUrl,Converter={StaticResource ImgUrlConverter}}" Stretch="Uniform" Grid.Row="0" Grid.RowSpan="2" Grid.Column="0" Grid.ColumnSpan="2"/> <TextBlock Text="{Binding Id}" Grid.Row="0" Grid.Column="0"/> <TextBlock Text="{Binding Name}" Grid.Row="0" Grid.Column="1"/> <TextBlock Text="{Binding ISBN}" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/> <behavior:Interaction.Behaviors> <local:GridBehavior RightButtonSaveCommand="{Binding DataContext.SaveCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CmdPara="{Binding}" /> </behavior:Interaction.Behaviors> <!--<behavior:Interaction.Triggers> <behavior:EventTrigger EventName="MouseUp"> <behavior:InvokeCommandAction Command="{Binding DataContext.SaveCommand2,RelativeSource={RelativeSource AncestorType=Window}}"/> </behavior:EventTrigger> </behavior:Interaction.Triggers>--> </Grid> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> </Window> using Microsoft.Xaml.Behaviors; using System.Collections.ObjectModel; using System.ComponentModel; using System.Globalization; 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; namespace WpfApp16 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } public class MainVM : INotifyPropertyChanged { public ICommand SaveCommand { get; set; } public ICommand SaveCommand2 { get; set; } public MainVM() { Task.Run(async () => { await InitBooksCollectionAsync(); }); SaveCommand = new DelCommand(SaveCommandExecuted); SaveCommand2 = new DelCommand(SaveCommand2Executed); } private void SaveCommand2Executed(object? obj) { } private void SaveCommandExecuted(object? obj) { var bk = obj as Book; if (bk != null) { System.Diagnostics.Debug.WriteLine(bk.ToString()); } } private async Task InitBooksCollectionAsync(int cnt = 100000000) { var 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>(); for (int i = 1; i < cnt + 1; i++) { booksList.Add(new Book() { Id = i, Name = $"Name_{i}", //ISBN=$"ISBN_{i}_{Guid.NewGuid()}", ImgUrl = $"{imgs[i % imgsCount]}" }); if (i % 1000000 == 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 (var bk in tempList) { BooksCollection.Add(bk); } MainTitle = $"{DateTime.Now},loaded {BooksCollection.Count} items,{GetMem()}"; }, System.Windows.Threading.DispatcherPriority.Background); } private string GetMem() { return $"Memory:{System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024:N2} M"; } private string mainTitle = "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 GridBehavior : Behavior<Grid> { private ContextMenu contextMenu; private MenuItem saveItem; public GridBehavior() { } private void Mi_Click(object sender, RoutedEventArgs e) { RightButtonSaveCommand?.Execute(CmdPara); } protected override void OnAttached() { CreateContextMenu(); base.OnAttached(); AssociatedObject.ContextMenu = contextMenu; } private void CreateContextMenu() { contextMenu = new ContextMenu(); saveItem = new MenuItem(); saveItem.Header = "Save"; saveItem.Click += Mi_Click; contextMenu.Items.Add(saveItem); } protected override void OnDetaching() { base.OnDetaching(); if(saveItem!=null) { saveItem.Click -= Mi_Click; saveItem = null; } if(contextMenu!=null) { contextMenu.Items.Clear(); contextMenu = null; } AssociatedObject.ContextMenu = null; } public ICommand RightButtonSaveCommand { get { return (ICommand)GetValue(RightButtonSaveCommandProperty); } set { SetValue(RightButtonSaveCommandProperty, value); } } // Using a DependencyProperty as the backing store for RightButtonSaveCommand. This enables animation, styling, binding, etc... public static readonly DependencyProperty RightButtonSaveCommandProperty = DependencyProperty.Register(nameof(RightButtonSaveCommand), typeof(ICommand), typeof(GridBehavior), 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(GridBehavior), new PropertyMetadata(null)); } 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?.Invoke(parameter); } } public class ImgUrlConverter : IValueConverter { private Dictionary<string, ImageSource> imgCache = new Dictionary<string, ImageSource>(); private static readonly object objLock = new object(); public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { string imgUrl = value?.ToString(); if (string.IsNullOrWhiteSpace(imgUrl) || !File.Exists(imgUrl)) { return null; } lock (objLock) { if (imgCache.TryGetValue(imgUrl, out ImageSource img)) { return img; } using (FileStream fs = new FileStream(imgUrl, FileMode.Open, FileAccess.Read)) { BitmapImage bmi = new BitmapImage(); bmi.BeginInit(); bmi.StreamSource = fs; bmi.CacheOption = BitmapCacheOption.OnLoad; bmi.EndInit(); if (bmi.CanFreeze) { bmi.Freeze(); } imgCache.TryAdd(imgUrl, bmi); return bmi; } } } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public class Book { public int Id { get; set; } public string Name { get; set; } public string ISBN { get; set; } public string ImgUrl { get; set; } public override string ToString() { return $"{DateTime.Now},Id:{Id},Name:{Name},ISBN:{ISBN},ImgUrl:{ImgUrl}"; } } }
2026-04-22 20:02:42,Id:40,Name:Name_40,ISBN:,ImgUrl:../../../Images\316.jpg 2026-04-22 20:02:49,Id:91,Name:Name_91,ISBN:,ImgUrl:../../../Images\370.jpg 2026-04-22 20:02:56,Id:77817208,Name:Name_77817208,ISBN:,ImgUrl:../../../Images\324.jpg





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