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

MVVM模式下ScrollViewer的Attached Property绑定值无法更新问题

问题根因

你当前的附加属性仅实现了「从ViewModel属性变更同步到ScrollViewer滚动位置」的单向逻辑,缺少监听ScrollViewer自身滚动事件、把滚动值回写到附加属性的逻辑,所以用户手动滚动时绑定源不会收到更新,ScrollValue的setter自然不会触发。
同时存在两个隐藏问题:

  • 附加属性未默认设置双向绑定模式,即使XAML手动加了Mode=TwoWay,属性元数据未开启的话也可能存在兼容问题
  • 未处理ScrollableHeight为0的边界情况,容易触发除以0异常

修复后的ScrollViewerBehavior代码

public static class ScrollViewerBehavior
{
    public static double GetScrollState(DependencyObject obj)
    {
        return (double)obj.GetValue(ScrollStateProperty);
    }

    public static void SetScrollState(DependencyObject obj, double value)
    {
        obj.SetValue(ScrollStateProperty, value);
    }

    public static readonly DependencyProperty ScrollStateProperty =
        DependencyProperty.RegisterAttached(
            "ScrollState", 
            typeof(double), 
            typeof(ScrollViewerBehavior), 
            new FrameworkPropertyMetadata(
                0.0, 
                // 开启默认双向绑定
                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, 
                OnScrollStateChanged));

    private static void OnScrollStateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        if (o is not ScrollViewer scrollViewer) return;

        if (scrollViewer.ScrollableHeight <= 0)
        {
            // 内容未加载完成时,延迟到加载完成后处理
            scrollViewer.Loaded += (s, _) =>
            {
                var sv = s as ScrollViewer;
                double newVO = (double)e.NewValue * sv.ScrollableHeight;
                if (Math.Abs(sv.VerticalOffset - newVO) > 0.1)
                {
                    sv.ScrollToVerticalOffset(newVO);
                }
            };
            return;
        }

        double newOffset = (double)e.NewValue * scrollViewer.ScrollableHeight;
        // 避免值一致时重复触发滚动
        if (Math.Abs(scrollViewer.VerticalOffset - newOffset) > 0.1)
        {
            scrollViewer.ScrollToVerticalOffset(newOffset);
        }

        // 首次附加属性时订阅滚动事件,避免重复订阅
        scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
        scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
    }

    private static void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        var scrollViewer = sender as ScrollViewer;
        if (scrollViewer == null || scrollViewer.ScrollableHeight <= 0) return;

        // 计算滚动百分比,范围0~1
        double scrollPercent = scrollViewer.VerticalOffset / scrollViewer.ScrollableHeight;
        double currentState = GetScrollState(scrollViewer);
        // 仅当值变化超过误差范围时更新,避免循环触发和无意义赋值
        if (Math.Abs(scrollPercent - currentState) > 0.001)
        {
            SetScrollState(scrollViewer, scrollPercent);
        }
    }
}

其他调整说明

  • 现有XAML、ViewModel代码无需修改,只要基类BaseViewModel的OnPropertyChanged逻辑正常即可运行
  • 如果需要直接存储滚动偏移量而非百分比,删掉代码中除以ScrollableHeight的逻辑即可,可根据业务需求自行调整

内容的提问来源于stack exchange,提问作者The Compiler

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 19:06:05