WPF customize behavior

Install-Package Microsoft.Xaml.Behaviors.Wpf

 

public class GridBehavior : Behavior<Grid>
{
    private ContextMenu contextMenu;
    public GridBehavior()
    {
        contextMenu = new ContextMenu();
        MenuItem saveMenu = new MenuItem();
        saveMenu.Header = "Save Selected Items";
        saveMenu.Click += SaveMenu_Click;
        contextMenu.Items.Add(saveMenu);

    }

    private void SaveMenu_Click(object sender, RoutedEventArgs e)
    {
        if (SaveSelectedItemsCommand != null && SaveSelectedItemsCommand.CanExecute(SaveCommandParameter))
        {
            SaveSelectedItemsCommand?.Execute(SaveCommandParameter);
        }
    }

    public ICommand SaveSelectedItemsCommand
    {
        get { return (ICommand)GetValue(SaveSelectedItemsCommandProperty); }
        set { SetValue(SaveSelectedItemsCommandProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SaveSelectedItemsCommand.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SaveSelectedItemsCommandProperty =
        DependencyProperty.Register("SaveSelectedItemsCommand", typeof(ICommand),
            typeof(GridBehavior), new PropertyMetadata(null));




    public object SaveCommandParameter
    {
        get { return (object)GetValue(SaveCommandParameterProperty); }
        set { SetValue(SaveCommandParameterProperty, value); }
    }

    // Using a DependencyProperty as the backing store for SaveCommandParameter.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SaveCommandParameterProperty =
        DependencyProperty.Register("SaveCommandParameter", typeof(object),
            typeof(GridBehavior), new PropertyMetadata(null));



    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.ContextMenu = contextMenu;
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.ContextMenu = null;
    }
}

 

<ItemsControl ItemsSource="{Binding BooksCollection}">
    <ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer CanContentScroll="True">
                <VirtualizingStackPanel IsItemsHost="True"
                                        ScrollViewer.CanContentScroll="True"
                                        ScrollViewer.IsDeferredScrollingEnabled="True"
                                        VirtualizingPanel.IsVirtualizing="True"
                                        VirtualizingPanel.VirtualizationMode="Recycling"
                                        VirtualizingPanel.CacheLength="2,2"
                                        VirtualizingPanel.CacheLengthUnit="Item"
                                        UseLayoutRounding="True"
                                        SnapsToDevicePixels="True"
                                        />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid Width="{x:Static SystemParameters.FullPrimaryScreenWidth}"
                  Height="{x:Static SystemParameters.FullPrimaryScreenHeight}">
                <!--<behavior:Interaction.Triggers>
                    <behavior:EventTrigger EventName="MouseLeftButtonUp">
                        <behavior:InvokeCommandAction 
                            Command="{Binding DataContext.SelectCommand,
                            RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                               CommandParameter="{Binding}"/>
                    </behavior:EventTrigger>
                </behavior:Interaction.Triggers>-->
                <behavior:Interaction.Behaviors>
                    <local:GridBehavior 
                        SaveSelectedItemsCommand="{Binding DataContext.SaveCommand,
                        RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                        SaveCommandParameter="{Binding}"/>
                </behavior:Interaction.Behaviors>
                <Image Source="{Binding ImgUrl,Converter={StaticResource ImgUrlConverter}}"
                       Stretch="Fill"/>
                <TextBlock Text="{Binding Id}">
                    <TextBlock.Style>
                        <Style TargetType="TextBlock">
                            <Setter Property="FontSize" Value="50"/>
                            <Setter Property="HorizontalAlignment" Value="Center"/>
                            <Setter Property="VerticalAlignment" Value="Center"/>
                            <Style.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter Property="FontSize" Value="100"/>
                                    <Setter Property="Foreground" Value="Red"/>
                                </Trigger>
                            </Style.Triggers>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

 

 

<Window x:Class="WpfApp14.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:WpfApp14"
        mc:Ignorable="d"
        Title="{Binding MainTitle}"
        WindowState="Maximized">
    <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"
                                                ScrollViewer.CanContentScroll="True"
                                                ScrollViewer.IsDeferredScrollingEnabled="True"
                                                VirtualizingPanel.IsVirtualizing="True"
                                                VirtualizingPanel.VirtualizationMode="Recycling"
                                                VirtualizingPanel.CacheLength="2,2"
                                                VirtualizingPanel.CacheLengthUnit="Item"
                                                UseLayoutRounding="True"
                                                SnapsToDevicePixels="True"
                                                />
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid Width="{x:Static SystemParameters.FullPrimaryScreenWidth}"
                          Height="{x:Static SystemParameters.FullPrimaryScreenHeight}">
                        <!--<behavior:Interaction.Triggers>
                            <behavior:EventTrigger EventName="MouseLeftButtonUp">
                                <behavior:InvokeCommandAction 
                                    Command="{Binding DataContext.SelectCommand,
                                    RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                       CommandParameter="{Binding}"/>
                            </behavior:EventTrigger>
                        </behavior:Interaction.Triggers>-->
                        <behavior:Interaction.Behaviors>
                            <local:GridBehavior 
                                SaveSelectedItemsCommand="{Binding DataContext.SaveCommand,
                                RelativeSource={RelativeSource AncestorType=ItemsControl}}"
                                SaveCommandParameter="{Binding}"/>
                        </behavior:Interaction.Behaviors>
                        <Image Source="{Binding ImgUrl,Converter={StaticResource ImgUrlConverter}}"
                               Stretch="Fill"/>
                        <TextBlock Text="{Binding Id}">
                            <TextBlock.Style>
                                <Style TargetType="TextBlock">
                                    <Setter Property="FontSize" Value="50"/>
                                    <Setter Property="HorizontalAlignment" Value="Center"/>
                                    <Setter Property="VerticalAlignment" Value="Center"/>
                                    <Style.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter Property="FontSize" Value="100"/>
                                            <Setter Property="Foreground" Value="Red"/>
                                        </Trigger>
                                    </Style.Triggers>
                                </Style>
                            </TextBlock.Style>
                        </TextBlock>
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Grid>
</Window>


using Microsoft.Xaml.Behaviors;
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
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 WpfApp14
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }


    public class GridBehavior : Behavior<Grid>
    {
        private ContextMenu contextMenu;
        public GridBehavior()
        {
            contextMenu = new ContextMenu();
            MenuItem saveMenu = new MenuItem();
            saveMenu.Header = "Save Selected Items";
            saveMenu.Click += SaveMenu_Click;
            contextMenu.Items.Add(saveMenu);

        }

        private void SaveMenu_Click(object sender, RoutedEventArgs e)
        {
            if (SaveSelectedItemsCommand != null && SaveSelectedItemsCommand.CanExecute(SaveCommandParameter))
            {
                SaveSelectedItemsCommand?.Execute(SaveCommandParameter);
            }
        }

        public ICommand SaveSelectedItemsCommand
        {
            get { return (ICommand)GetValue(SaveSelectedItemsCommandProperty); }
            set { SetValue(SaveSelectedItemsCommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SaveSelectedItemsCommand.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SaveSelectedItemsCommandProperty =
            DependencyProperty.Register("SaveSelectedItemsCommand", typeof(ICommand),
                typeof(GridBehavior), new PropertyMetadata(null));




        public object SaveCommandParameter
        {
            get { return (object)GetValue(SaveCommandParameterProperty); }
            set { SetValue(SaveCommandParameterProperty, value); }
        }

        // Using a DependencyProperty as the backing store for SaveCommandParameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SaveCommandParameterProperty =
            DependencyProperty.Register("SaveCommandParameter", typeof(object),
                typeof(GridBehavior), new PropertyMetadata(null));



        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.ContextMenu = contextMenu;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            AssociatedObject.ContextMenu = null;
        }
    }

    public class MainVM : INotifyPropertyChanged
    {
        private DateTime startTime;
        public MainVM()
        {
            SaveCommand = new DelCommand(SaveCommandExecuted);
            SelectCommand = new DelCommand(SelectCommandExecuted);
            Task.Run(async () =>
            {
                await InitBooksCollectionAsync();
            });
        }

        private void SelectCommandExecuted(object? obj)
        {
            var bk = obj as Book;
            if (bk != null)
            {
                SelectedBk = bk;
                System.Diagnostics.Debug.WriteLine(SelectedBk.ToString());
            }
        }

        private void SaveCommandExecuted(object? obj)
        {
            var bk = obj as Book;
            if (bk != null)
            {
                SelectedBk = bk;
                System.Diagnostics.Debug.WriteLine(SelectedBk.ToString());
            }
        }

        private Book selectedBk;
        public Book SelectedBk
        {
            get
            {
                return selectedBk;
            }
            set
            {
                if (value != selectedBk)
                {
                    selectedBk = value;
                    OnPropertyChanged();
                }
            }
        }

        public ICommand SaveCommand { get; set; }
        public ICommand SelectCommand { get; set; }
        private async Task InitBooksCollectionAsync(long cnt = 300000001)
        {
            var imgDir = @"../../../Images";
            if (!Directory.Exists(imgDir))
            {
                return;
            }

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

            startTime = DateTime.Now;
            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,
                    ImgUrl = $"{imgs[i % imgsCount]}"
                });

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

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

        private async Task PopulateBooksCollectionAsync(List<Book> booksList, long i)
        {
            var tempList = booksList.ToList();
            booksList.Clear();
            await Application.Current.Dispatcher.InvokeAsync(() =>
            {
                foreach (var bk in tempList)
                {
                    BooksCollection.Add(bk);
                }
                MainTitle = $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")},i:{i},loaded {BooksCollection.Count} items,{GetMem()},{GetTimeCost()}";
                System.Diagnostics.Debug.WriteLine(MainTitle);
            }, System.Windows.Threading.DispatcherPriority.Background);
        }

        public string GetTimeCost()
        {
            return $"time cost {DateTime.Now.Subtract(startTime).TotalSeconds:N2} seconds";
        }

        private string GetMem()
        {
            return $"memory {System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024:N2} M";
        }

        private string mainTitle;
        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 static class SelectorBehavior
    {
        public static readonly DependencyProperty SelectionChangedCommandProperty =
            DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(ICommand), typeof(SelectorBehavior), new PropertyMetadata(null, OnCommandChanged));

        public static void SetSelectionChangedCommand(DependencyObject obj, ICommand value)
            => obj.SetValue(SelectionChangedCommandProperty, value);

        public static ICommand GetSelectionChangedCommand(DependencyObject obj)
            => (ICommand)obj.GetValue(SelectionChangedCommandProperty);

        private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Selector selector)
            {
                selector.SelectionChanged -= OnSelectionChanged;
                if (e.NewValue != null)
                    selector.SelectionChanged += OnSelectionChanged;
            }
        }

        private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var command = GetSelectionChangedCommand(sender as DependencyObject);
            if (command != null && command.CanExecute(e))
                command.Execute(e.AddedItems);
        }
    }

    public class DelCommand : ICommand
    {
        public event EventHandler? CanExecuteChanged;
        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 bool CanExecute(object? parameter)
        {
            return canExecute == null ? true : canExecute(parameter);
        }

        public void Execute(object? parameter)
        {
            execute?.Invoke(parameter);
        }
    }

    public class ImgUrlConverter : IValueConverter
    {
        private static readonly object objLock = new object();
        private Dictionary<string, ImageSource> imgCache = new Dictionary<string, ImageSource>();
        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 imageSource))
                {
                    return imageSource;
                }

                using (FileStream fs = new FileStream(imgUrl, FileMode.Open, FileAccess.Read, FileShare.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 ImgUrl { get; set; }

        public override string ToString()
        {
            return $"Id:{Id},ImgUrl:{ImgUrl}";
        }
    }
}

 

 

image

 

 

image

 

 

 

 

 

 

image

 

The thread '.NET TP Worker' (19800) has exited with code 0 (0x0).
The thread '.NET TP Worker' (22288) has exited with code 0 (0x0).
The thread '.NET TP Worker' (16372) has exited with code 0 (0x0).
Id:1,ImgUrl:../../../Images\1108.jpg
Id:134513275,ImgUrl:../../../Images\398.jpg
Id:300000001,ImgUrl:../../../Images\1191.jpg
The thread '.NET TP Worker' (3300) has exited with code 0 (0x0).
The thread '.NET TP Worker' (3900) has exited with code 0 (0x0).
Id:299999959,ImgUrl:../../../Images\986.jpg
Id:299999944,ImgUrl:../../../Images\415.jpg
Id:299999929,ImgUrl:../../../Images\395.jpg
Id:299999917,ImgUrl:../../../Images\382.jpg
Id:299999893,ImgUrl:../../../Images\356.jpg
Id:299999872,ImgUrl:../../../Images\332.jpg

 

image

 

posted @ 2026-04-11 20:46  FredGrit  阅读(2)  评论(0)    收藏  举报