WPF ListBox ScrollViewer get scrolled current item actual vertical position in the whole scrollview of listbox via mvvm
<i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseWheel"> <i:InvokeCommandAction Command="{Binding MouseWheelCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBox}}}" /> </i:EventTrigger> </i:Interaction.Triggers> double perItemHeight = 0.0d; ListBoxItem firstItem; private void MouseWheelCommandExecuted(object? obj) { var lbx = obj as ListBox; if (lbx == null) { return; } if (firstItem == null) { firstItem = lbx.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem; if (firstItem != null) { perItemHeight = firstItem.ActualHeight; } } var scrollViewer = FindVisualChild<ScrollViewer>(lbx); if (scrollViewer != null) { scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; //double viewportHeight = scrollViewer.ViewportHeight; //double extentHeight = scrollViewer.ExtentHeight; //double verticalOffset = scrollViewer.VerticalOffset; //double currentVerticalOffset = scrollViewer.VerticalOffset; //double maxVerticalOffset = scrollViewer.ExtentHeight - scrollViewer.ViewportHeight; //// Display or use the current scroll position //Debug.WriteLine($"Current Y position: {currentVerticalOffset}"); //Debug.WriteLine($"Max Y position: {maxVerticalOffset}"); //// You can also calculate percentage scrolled //double percentageScrolled = currentVerticalOffset / maxVerticalOffset; //Debug.WriteLine($"Percentage scrolled: {percentageScrolled:P}"); } } private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { PosY = (e.VerticalOffset + 1) * perItemHeight; } public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is T) { return (T)child; } else { T childOfChild = FindVisualChild<T>(child); if (childOfChild != null) { return childOfChild; } } } return null; }
install-package Microsoft.Xaml.Behaviors.Wpf
//xaml <Window x:Class="WpfApp45.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:i="http://schemas.microsoft.com/xaml/behaviors" WindowState="Maximized" xmlns:local="clr-namespace:WpfApp45" mc:Ignorable="d" Title="{Binding PosY,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Height="450" Width="800"> <Grid Background="#000000"> <ListBox x:Name="lbx" Focusable="True" SelectedIndex="{Binding SelectedIdx,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding BooksCollection,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectionMode="Extended"> <i:Interaction.Triggers> <i:EventTrigger EventName="PreviewMouseWheel"> <i:InvokeCommandAction Command="{Binding MouseWheelCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBox}}}" /> </i:EventTrigger> </i:Interaction.Triggers> <ListBox.ItemTemplate> <DataTemplate> <Grid> <Grid.Background> <ImageBrush ImageSource="{Binding ImgSource}"/> </Grid.Background> <Grid.Resources> <Style TargetType="{x:Type Grid}"> <Setter Property="Height" Value="300"/> </Style> <Style TargetType="{x:Type TextBlock}"> <Setter Property="FontSize" Value="30"/> <Setter Property="Foreground" Value="Black"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="FontSize" Value="80"/> <Setter Property="Foreground" Value="Red"/> </Trigger> </Style.Triggers> </Style> </Grid.Resources> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <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="0" Grid.Column="2" Text="{Binding Title}"/> <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Text="{Binding ISBN}"/> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window> //cs using Microsoft.Win32; using System.Collections; using System.Collections.ObjectModel; using System.ComponentModel; 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.Xml; namespace WpfApp45 { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var vm = new MainVM(); this.DataContext = vm; } } public class MainVM : INotifyPropertyChanged { public MainVM() { InitBooksCollection(); MouseWheelCommand = new DelCommand(MouseWheelCommandExecuted); } double perItemHeight = 0.0d; ListBoxItem firstItem; private void MouseWheelCommandExecuted(object? obj) { var lbx = obj as ListBox; if (lbx == null) { return; } if (firstItem == null) { firstItem = lbx.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem; if (firstItem != null) { perItemHeight = firstItem.ActualHeight; } } var scrollViewer = FindVisualChild<ScrollViewer>(lbx); if (scrollViewer != null) { scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; //double viewportHeight = scrollViewer.ViewportHeight; //double extentHeight = scrollViewer.ExtentHeight; //double verticalOffset = scrollViewer.VerticalOffset; //double currentVerticalOffset = scrollViewer.VerticalOffset; //double maxVerticalOffset = scrollViewer.ExtentHeight - scrollViewer.ViewportHeight; //// Display or use the current scroll position //Debug.WriteLine($"Current Y position: {currentVerticalOffset}"); //Debug.WriteLine($"Max Y position: {maxVerticalOffset}"); //// You can also calculate percentage scrolled //double percentageScrolled = currentVerticalOffset / maxVerticalOffset; //Debug.WriteLine($"Percentage scrolled: {percentageScrolled:P}"); } } private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) { PosY = (e.VerticalOffset + 1) * perItemHeight; } public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is T) { return (T)child; } else { T childOfChild = FindVisualChild<T>(child); if (childOfChild != null) { return childOfChild; } } } return null; } private void InitBooksCollection() { BooksCollection = new ObservableCollection<Book>(); var dir = @"../../../Images"; if (Directory.Exists(dir)) { var imgs = Directory.GetFiles(dir); int imgsCount = imgs.Count(); for (int i = 0; i < 1000; i++) { BooksCollection.Add(new Book() { Id = i + 1, Name = $"Name_{i + 1}", Title = $"Title_{i + 1}", ISBN = $"{Guid.NewGuid().ToString("N")}", ImgSource = GetImgSourceVirUrl(imgs[i % imgsCount]), }); } } } private ImageSource GetImgSourceVirUrl(string imgUrl) { BitmapImage bmi = new BitmapImage(); bmi.BeginInit(); bmi.UriSource = new Uri(imgUrl, UriKind.RelativeOrAbsolute); bmi.EndInit(); if (bmi.CanFreeze) { bmi.Freeze(); } return bmi; } public event PropertyChangedEventHandler? PropertyChanged; public void OnPropertyChanged([CallerMemberName] string propertyName = "") { var handler = PropertyChanged; if (handler != null) { handler?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } private ObservableCollection<Book> booksCollection; public ObservableCollection<Book> BooksCollection { get { return booksCollection ?? (booksCollection = new ObservableCollection<Book>()); } set { if (value != null) { booksCollection = value; OnPropertyChanged(); } } } private ObservableCollection<Book> selectedBooks; public ObservableCollection<Book> SelectedBooks { get { return selectedBooks; } set { if (value != selectedBooks) { selectedBooks = value; OnPropertyChanged(nameof(SelectedBooks)); } } } private bool isSelected; public bool IsSelected { get { return isSelected; } set { if (value != isSelected) { isSelected = value; OnPropertyChanged(); } } } private IList _selectedItems; public IList SelectedItems { get => _selectedItems; set { _selectedItems = value; OnPropertyChanged(); } } private int selectedIdx; public int SelectedIdx { get { return selectedIdx; } set { if (value != selectedIdx) { selectedIdx = value; OnPropertyChanged(); } } } private double posY; public double PosY { get { return posY; } set { if (value != posY) { posY = value; OnPropertyChanged(); } } } public ICommand MouseWheelCommand { get; set; } } public class DelCommand : ICommand { private Action<object?> execcute; private Predicate<object?> canExecute; public DelCommand(Action<object?> execcuteValue, Predicate<object?> canExecuteValue = null) { execcute = execcuteValue; 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) { execcute(parameter); } } public class Book { public int Id { get; set; } public string Name { get; set; } public string Title { get; set; } public string ISBN { get; set; } public ImageSource ImgSource { get; set; } } }