You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 20:25:10