WinUI 3中基于MVVM模式实现ListView编程式滚动的问题
基于MVVM实现WinUI 3 ListView编程式滚动方案
不需要在ViewModel中直接访问ListView实例,通过附加属性或行为即可实现完全解耦的滚动逻辑,符合MVVM原则且无需修改代码后台。
方案一:附加属性(推荐,更贴合MVVM状态驱动)
通过自定义附加属性,让ViewModel维护「当前要滚动到的项」,View监听该状态变化并自动执行滚动。
1. 创建附加属性辅助类
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace YourAppNamespace.Helpers { public static class ListViewScrollHelper { // 绑定ViewModel中要滚动到的目标项 public static readonly DependencyProperty ScrollToItemProperty = DependencyProperty.RegisterAttached( "ScrollToItem", typeof(object), typeof(ListViewScrollHelper), new PropertyMetadata(null, OnScrollToItemChanged)); public static object GetScrollToItem(DependencyObject obj) { return obj.GetValue(ScrollToItemProperty); } public static void SetScrollToItem(DependencyObject obj, object value) { obj.SetValue(ScrollToItemProperty, value); } private static void OnScrollToItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is ListView listView && e.NewValue != null) { // 滚动到目标项并置顶 listView.ScrollIntoView(e.NewValue, ScrollIntoViewAlignment.Leading); } } } }
2. 修改ViewModel逻辑
维护当前滚动项,点击按钮时更新目标项:
using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; using System.Collections.ObjectModel; using System.Linq; namespace UcViewModel { public class MainViewModel : ObservableObject { // 列表数据源 public ObservableCollection<ListItemViewModel> ListOfListItems { get; } = new(); // 当前要滚动到的项 private ListItemViewModel _currentScrollItem; public ListItemViewModel CurrentScrollItem { get => _currentScrollItem; set => SetProperty(ref _currentScrollItem, value); } // 向上滚动命令 private DelegateCommand _scrollUpCommand; public ICommand ScrollUpCommand => _scrollUpCommand ??= new DelegateCommand(PerformScrollUp); private void PerformScrollUp() { if (!ListOfListItems.Any()) return; if (CurrentScrollItem == null) { // 初始状态滚动到第一项 CurrentScrollItem = ListOfListItems.First(); return; } var currentIndex = ListOfListItems.IndexOf(CurrentScrollItem); if (currentIndex > 0) { CurrentScrollItem = ListOfListItems[currentIndex - 1]; } } // 向下滚动命令 private DelegateCommand _scrollDownCommand; public ICommand ScrollDownCommand => _scrollDownCommand ??= new DelegateCommand(PerformScrollDown); private void PerformScrollDown() { if (!ListOfListItems.Any()) return; if (CurrentScrollItem == null) { CurrentScrollItem = ListOfListItems.First(); return; } var currentIndex = ListOfListItems.IndexOf(CurrentScrollItem); if (currentIndex < ListOfListItems.Count - 1) { CurrentScrollItem = ListOfListItems[currentIndex + 1]; } } } }
3. XAML绑定配置
<!-- 引入附加属性命名空间 --> xmlns:helpers="using:YourAppNamespace.Helpers" <!-- ListView绑定附加属性 --> <ListView SelectionMode="None" ItemsSource="{Binding ListOfListItems}" helpers:ListViewScrollHelper.ScrollToItem="{Binding CurrentScrollItem}"> <ListView.ItemTemplate> <DataTemplate x:DataType="UcViewModel:ListItemViewModel"> <Uc:ListItemUserControl/> </DataTemplate> </ListView.ItemTemplate> </ListView> <!-- 滚动按钮 --> <Button Content="Scroll up" Command="{Binding ScrollUpCommand}"/> <Button Content="Scroll down" Command="{Binding ScrollDownCommand}"/>
方案二:使用WinUI行为
通过NuGet行为库,直接在View层绑定ViewModel的命令,由行为处理滚动逻辑。
1. 安装依赖包
安装NuGet包:Microsoft.Xaml.Behaviors.WinUI.Managed
2. 创建滚动行为类
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.Xaml.Behaviors; using System.Windows.Input; namespace YourAppNamespace.Behaviors { public class ListViewScrollBehavior : Behavior<ListView> { // 向上滚动命令 public static readonly DependencyProperty ScrollUpCommandProperty = DependencyProperty.Register( nameof(ScrollUpCommand), typeof(ICommand), typeof(ListViewScrollBehavior), new PropertyMetadata(null)); public ICommand ScrollUpCommand { get => (ICommand)GetValue(ScrollUpCommandProperty); set => SetValue(ScrollUpCommandProperty, value); } // 向下滚动命令 public static readonly DependencyProperty ScrollDownCommandProperty = DependencyProperty.Register( nameof(ScrollDownCommand), typeof(ICommand), typeof(ListViewScrollBehavior), new PropertyMetadata(null)); public ICommand ScrollDownCommand { get => (ICommand)GetValue(ScrollDownCommandProperty); set => SetValue(ScrollDownCommandProperty, value); } protected override void OnAttached() { base.OnAttached(); // 绑定命令执行逻辑 ScrollUpCommand?.CanExecuteChanged += (s, e) => { }; ScrollDownCommand?.CanExecuteChanged += (s, e) => { }; } protected override void OnDetaching() { base.OnDetaching(); } // 处理向上滚动 private void ExecuteScrollUp() { var listView = AssociatedObject; if (listView?.Items == null || listView.Items.Count == 0) return; // 获取当前可视区域的第一个项 var firstVisibleItem = listView.Items.Cast<object>().FirstOrDefault(item => listView.IsItemVisible(item)); if (firstVisibleItem == null) { listView.ScrollIntoView(listView.Items[0], ScrollIntoViewAlignment.Leading); return; } var index = listView.Items.IndexOf(firstVisibleItem); if (index > 0) { listView.ScrollIntoView(listView.Items[index - 1], ScrollIntoViewAlignment.Leading); } } // 处理向下滚动 private void ExecuteScrollDown() { var listView = AssociatedObject; if (listView?.Items == null || listView.Items.Count == 0) return; var lastVisibleItem = listView.Items.Cast<object>().LastOrDefault(item => listView.IsItemVisible(item)); if (lastVisibleItem == null) { listView.ScrollIntoView(listView.Items[0], ScrollIntoViewAlignment.Leading); return; } var index = listView.Items.IndexOf(lastVisibleItem); if (index < listView.Items.Count - 1) { listView.ScrollIntoView(listView.Items[index + 1], ScrollIntoViewAlignment.Leading); } } } }
3. XAML配置行为
xmlns:behaviors="using:Microsoft.Xaml.Behaviors" xmlns:customBehaviors="using:YourAppNamespace.Behaviors" <ListView SelectionMode="None" ItemsSource="{Binding ListOfListItems}"> <behaviors:Interaction.Behaviors> <customBehaviors:ListViewScrollBehavior ScrollUpCommand="{Binding ScrollUpCommand}" ScrollDownCommand="{Binding ScrollDownCommand}"/> </behaviors:Interaction.Behaviors> <ListView.ItemTemplate> <DataTemplate x:DataType="UcViewModel:ListItemViewModel"> <Uc:ListItemUserControl/> </DataTemplate> </ListView.ItemTemplate> </ListView> <Button Content="Scroll up" Command="{Binding ScrollUpCommand}"/> <Button Content="Scroll down" Command="{Binding ScrollDownCommand}"/>
4. ViewModel简化版
using Microsoft.Toolkit.Mvvm.Input; namespace UcViewModel { public class MainViewModel { private DelegateCommand _scrollUpCommand; public ICommand ScrollUpCommand => _scrollUpCommand ??= new DelegateCommand(() => { }); private DelegateCommand _scrollDownCommand; public ICommand ScrollDownCommand => _scrollDownCommand ??= new DelegateCommand(() => { }); // 列表数据源... } }
内容的提问来源于stack exchange,提问作者Hulabuli
相关产品推荐
相关产品推荐

