WPF ListBox scrollintoview in ViewModel via behavior

FredGrit發表於2024-10-25
 <ListBox Grid.Row="0"
          Grid.Column="0"
           IsSynchronizedWithCurrentItem="True"
          ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
          SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
          SelectedItem="{Binding SelectedBk,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
          VirtualizingPanel.IsContainerVirtualizable="True"
          VirtualizingPanel.IsVirtualizing="True"
          VirtualizingPanel.VirtualizationMode="Recycling"
          VirtualizingPanel.CacheLength="1"
          VirtualizingPanel.CacheLengthUnit="Item">
     <behavior:Interaction.Triggers>
         <behavior:EventTrigger EventName="SelectionChanged">
             <behavior:CallMethodAction MethodName="ListBox_SelectionChanged" TargetObject="{Binding}"/>
         </behavior:EventTrigger>
     </behavior:Interaction.Triggers>
     <ListBox.ItemTemplate>
         <DataTemplate>
             <Grid>
                 <Image Source="{Binding ImgUrl}"
                        Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"
                        Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"/>
                 <TextBlock Text="{Binding Id}"
                            FontSize="100"
                            Foreground="Red"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Center"
                            Panel.ZIndex="2"/>
             </Grid>
         </DataTemplate>
     </ListBox.ItemTemplate>
 </ListBox>



 public void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
 {
     var lbx = sender as ListBox;
     if (lbx != null)
     {
         Application.Current.Dispatcher.BeginInvoke(new Action(() =>
         {
             lbx.ScrollIntoView(SelectedBk);
         }));
     }
 }

//XAML
<Window x:Class="WpfApp23.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:WpfApp23"
        WindowState="Maximized"
        mc:Ignorable="d"
        Title="{Binding SelectedBk.Id,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
        Height="450" Width="800">
    <behavior:Interaction.Triggers>
        <behavior:EventTrigger EventName="KeyDown">
            <behavior:CallMethodAction MethodName="Window_KeyDown" TargetObject="{Binding}"/>
        </behavior:EventTrigger>
    </behavior:Interaction.Triggers>
    <Window.DataContext>
        <local:BookVM/>
    </Window.DataContext>
    <Window.Resources>
        <local:LengthConverter x:Key="lengthConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox Grid.Row="0"
                 Grid.Column="0"
                  IsSynchronizedWithCurrentItem="True"
                 ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 SelectedItem="{Binding SelectedBk,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
                 VirtualizingPanel.IsContainerVirtualizable="True"
                 VirtualizingPanel.IsVirtualizing="True"
                 VirtualizingPanel.VirtualizationMode="Recycling"
                 VirtualizingPanel.CacheLength="1"
                 VirtualizingPanel.CacheLengthUnit="Item">
            <behavior:Interaction.Triggers>
                <behavior:EventTrigger EventName="SelectionChanged">
                    <behavior:CallMethodAction MethodName="ListBox_SelectionChanged" TargetObject="{Binding}"/>
                </behavior:EventTrigger>
            </behavior:Interaction.Triggers>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding ImgUrl}"
                               Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"
                               Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"/>
                        <TextBlock Text="{Binding Id}"
                                   FontSize="100"
                                   Foreground="Red"
                                   VerticalAlignment="Center"
                                   HorizontalAlignment="Center"
                                   Panel.ZIndex="2"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox Grid.Row="0"
         Grid.Column="1"
                 IsSynchronizedWithCurrentItem="True"
         ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedItem="{Binding SelectedBk,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         VirtualizingPanel.IsContainerVirtualizable="True"
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.CacheLength="1"
         VirtualizingPanel.CacheLengthUnit="Item">
            <behavior:Interaction.Triggers>
                <behavior:EventTrigger EventName="SelectionChanged">
                    <behavior:CallMethodAction MethodName="ListBox_SelectionChanged" TargetObject="{Binding}"/>
                </behavior:EventTrigger>
            </behavior:Interaction.Triggers>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding ImgUrl}"
                       Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"
                       Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"/>
                        <TextBlock Text="{Binding Id}"
                           FontSize="100"
                           Foreground="Red"
                           VerticalAlignment="Center"
                           HorizontalAlignment="Center"
                           Panel.ZIndex="2"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox Grid.Row="1"
         Grid.Column="0"
         ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedItem="{Binding SelectedBk,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         VirtualizingPanel.IsContainerVirtualizable="True"
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.CacheLength="1"
         VirtualizingPanel.CacheLengthUnit="Item">
            <behavior:Interaction.Triggers>
                <behavior:EventTrigger EventName="SelectionChanged">
                    <behavior:CallMethodAction MethodName="ListBox_SelectionChanged" TargetObject="{Binding}"/>
                </behavior:EventTrigger>
            </behavior:Interaction.Triggers>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding ImgUrl}"
                       Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"
                       Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"/>
                        <TextBlock Text="{Binding Id}"
                           FontSize="100"
                           Foreground="Red"
                           VerticalAlignment="Center"
                           HorizontalAlignment="Center"
                           Panel.ZIndex="2"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <ListBox Grid.Row="1"
         Grid.Column="1"
         ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         SelectedItem="{Binding SelectedBk,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
         VirtualizingPanel.IsContainerVirtualizable="True"
         VirtualizingPanel.IsVirtualizing="True"
         VirtualizingPanel.VirtualizationMode="Recycling"
         VirtualizingPanel.CacheLength="1"
         VirtualizingPanel.CacheLengthUnit="Item">
            <behavior:Interaction.Triggers>
                <behavior:EventTrigger EventName="SelectionChanged">
                    <behavior:CallMethodAction MethodName="ListBox_SelectionChanged" TargetObject="{Binding}"/>
                </behavior:EventTrigger>
            </behavior:Interaction.Triggers>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Image Source="{Binding ImgUrl}"
                       Width="{Binding ActualWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"
                       Height="{Binding ActualHeight,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Window}},Converter={StaticResource lengthConverter}}"/>
                        <TextBlock Text="{Binding Id}"
                           FontSize="100"
                           Foreground="Red"
                           VerticalAlignment="Center"
                           HorizontalAlignment="Center"
                           Panel.ZIndex="2"/>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>



//cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 WpfApp23
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }



    public class BookVM : INotifyPropertyChanged
    { 
        public BookVM()
        { 
            InitData();
            InitSystemTimers();
        }

        private void InitSystemTimers()
        {
            System.Timers.Timer tmr = new System.Timers.Timer();
            tmr.Elapsed += Tmr_Elapsed;
            tmr.Interval = 1000;
            tmr.Start();
        }

        private void Tmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (isPaused)
            {
                return;
            }
            if (++SelectedIdx >= BooksCollection.Count)
            {
                SelectedIdx = 0;
            }
        }

        private void InitData()
        {
            imgsList = new List<string>(Directory.GetFiles(@"../../Images"));
            if (imgsList != null && imgsList.Any())
            {
                imgsCount = imgsList.Count;
                BooksCollection = new ObservableCollection<Book>();
                for (int i = 0; i < 10000; i++)
                {
                    BooksCollection.Add(new Book()
                    {
                        Id = i + 1,
                        Name = $"Name_{i + 1}",
                        ISBN = $"ISBN_{Guid.NewGuid().ToString("N")}",
                        ImgUrl = $"{imgsList[i % imgsCount]}",
                        ImgSource = GetImgSourceViaImgUrl(imgsList[i % imgsCount]),
                    });
                }
            }
        }

        public void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var lbx = sender as ListBox;
            if (lbx != null)
            {
                Application.Current.Dispatcher.BeginInvoke(new Action(() =>
                {
                    lbx.ScrollIntoView(SelectedBk);
                }));
            }
        }

        private ImageSource GetImgSourceViaImgUrl(string imgUrlValue)
        {
            try
            {
                if (!File.Exists(imgUrlValue))
                {
                    return null;
                }
                BitmapImage bmi = new BitmapImage();
                bmi.BeginInit();
                bmi.UriSource = new Uri(imgUrlValue, UriKind.RelativeOrAbsolute);
                bmi.EndInit();
                if (bmi.CanFreeze)
                {
                    bmi.Freeze();
                }
                return bmi;
            }
            catch (Exception ex)
            {
                MessageBox.Show($"{imgUrlValue}\n{ex.Message}");
                return null;
            }
        }


        public void Window_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.Key == Key.Space)
            {
                isPaused = !isPaused;
            }
        }

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

        private bool isPaused = false;

        private Book selectedBk;
        public Book SelectedBk
        {
            get
            {
                return selectedBk;
            }
            set
            {
                if (value != selectedBk)
                {
                    selectedBk = value;
                    OnPropertyChanged(nameof(SelectedBk));
                    System.Diagnostics.Debug.WriteLine(SelectedBk.ImgUrl);
                }
            }
        }

        private int selectedIdx;
        public int SelectedIdx
        {
            get
            {
                return selectedIdx;
            }
            set
            {
                if (value != selectedIdx)
                {
                    selectedIdx = value;
                    OnPropertyChanged(nameof(SelectedIdx));
                }
            }
        }

        private List<string> imgsList { get; set; }

        private int imgsCount { get; set; }

        private ObservableCollection<Book> booksCollection;
        public ObservableCollection<Book> BooksCollection
        {
            get
            {
                return booksCollection;
            }

            set
            {
                if (value != booksCollection)
                {
                    booksCollection = value;
                    OnPropertyChanged(nameof(BooksCollection));
                }
            }
        }
    }

    public class LengthConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            double d = 0;
            if (double.TryParse(value?.ToString(), out d))
            {
                return d/2;
            }
            return d;
        }

        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 ImageSource ImgSource { get; set; }
    }


}

相關文章