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
相关产品推荐
相关产品推荐

