ScrollViewer的VerticalOffset属性是否支持双向绑定?

9
我在Silverlight 3.0中有一个View和一个ViewModel。
View包含一个标准的ScrollViewer,其中包含动态内容。
根据ScrollViewer中的内容,用户可能已经滚动了一半的内容,然后执行了导致ScrollViewer加载新内容的操作,但是ScrollViewer不会自动滚动到顶部。
我想能够绑定到VerticalOffset属性,但它是只读的。有什么关于可附加行为的想法吗?
谢谢。

你想在ViewModel上公开一个属性,指示ScrollViewer应该在哪里吗?不清楚你想将VerticalOffset绑定到什么? - AnthonyWJones
4个回答

5
以下博客文章提供了一个已附加的行为,它公开了滚动视图器的垂直/水平偏移量,以便您可以将其绑定或在代码中设置:

http://blog.scottlogic.com/2010/07/21/exposing-and-binding-to-a-silverlight-scrollviewers-scrollbars.html

这允许以下标记:
<ScrollViewer 
    local:ScrollViewerBinding.VerticalOffset="{Binding YPosition, Mode=TwoWay}"
    local:ScrollViewerBinding.HorizontalOffset="{Binding XPosition, Mode=TwoWay}">
    <!-- Big content goes here! -->
</ScrollViewer>

我想看一篇文章(我需要WPF解决方案来绑定ScrollViewer),但链接是错误的。 - Sinatr
http://blog.scottlogic.com/2010/07/21/exposing-and-binding-to-a-silverlight-scrollviewers-scrollbars.html - Thomas
很棒的文章,我只需要使用事件scrollViewer.ScrollChanged,而不需要寻找ScrollBar - xmedeko

3

鉴于您正在使用ViewModel,我认为“导致ScrollViewer加载新内容的操作”是由对ViewModel进行更改而导致的。在这种情况下,我会向ViewModel添加一个事件,每当发生此类更改时触发该事件。

您的View可以在此事件上添加处理程序,并在其触发时调用ScrollViewer的ScrollToVerticalPosition方法。


3
我简化了@ColinE的解决方案。我不是钩住ScrollBar.ValueChanged事件,而是钩住ScrollViewer.ScrollChanged事件。因此,1. 不必在可视树中查找ScrollBar,2. 当ScrollViewer的内容更改时,在某些过渡状态下会调用ScrollBar.ValueChanged,我不想捕获这些状态。
我发布了我的代码来获取VerticalOffset,HorizontalOffset类似:
/// <summary>
/// VerticalOffset attached property
/// </summary>
public static readonly DependencyProperty VerticalOffsetProperty =
    DependencyProperty.RegisterAttached("VerticalOffset", typeof(double),
    typeof(ScrollViewerBinding), new FrameworkPropertyMetadata(double.NaN,
        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        OnVerticalOffsetPropertyChanged));

/// <summary>
/// Just a flag that the binding has been applied.
/// </summary>
private static readonly DependencyProperty VerticalScrollBindingProperty =
    DependencyProperty.RegisterAttached("VerticalScrollBinding", typeof(bool?), typeof(ScrollViewerBinding));

public static double GetVerticalOffset(DependencyObject depObj)
{
    return (double)depObj.GetValue(VerticalOffsetProperty);
}

public static void SetVerticalOffset(DependencyObject depObj, double value)
{
    depObj.SetValue(VerticalOffsetProperty, value);
}

private static void OnVerticalOffsetPropertyChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e)
{
    ScrollViewer scrollViewer = d as ScrollViewer;
    if (scrollViewer == null)
        return;

    BindVerticalOffset(scrollViewer);
    scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
}

public static void BindVerticalOffset(ScrollViewer scrollViewer)
{
    if (scrollViewer.GetValue(VerticalScrollBindingProperty) != null)
        return;

    scrollViewer.SetValue(VerticalScrollBindingProperty, true);
    scrollViewer.ScrollChanged += (s, se) =>
    {
        if (se.VerticalChange == 0)
            return;
        SetVerticalOffset(scrollViewer, se.VerticalOffset);
    };
}

并将其用于XAML中:

<ScrollViewer local:ScrollViewerBinding.VerticalOffset="{Binding ScrollVertical}">
    <!-- content ... -->
</ScrollViewer>

2

我最开始使用了这个,但是注意到没有清理阶段,所以这里提供完整的实现,包括可绑定的水平和垂直偏移量:

using System.Windows;
using System.Windows.Controls;

namespace Test
{
    public static class ScrollPositionBehavior
    {
        public static readonly DependencyProperty HorizontalOffsetProperty =
            DependencyProperty.RegisterAttached(
                "HorizontalOffset",
                typeof(double),
                typeof(ScrollPositionBehavior),
                new FrameworkPropertyMetadata(
                    double.NaN,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    OnHorizontalOffsetPropertyChanged));

        public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.RegisterAttached(
                "VerticalOffset",
                typeof(double),
                typeof(ScrollPositionBehavior),
                new FrameworkPropertyMetadata(
                    double.NaN,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    OnVerticalOffsetPropertyChanged));

        private static readonly DependencyProperty IsScrollPositionBoundProperty =
            DependencyProperty.RegisterAttached("IsScrollPositionBound", typeof(bool?), typeof(ScrollPositionBehavior));

        public static void BindOffset(ScrollViewer scrollViewer)
        {
            if (scrollViewer.GetValue(IsScrollPositionBoundProperty) is true)
                return;

            scrollViewer.SetValue(IsScrollPositionBoundProperty, true);

            scrollViewer.Loaded += ScrollViewer_Loaded;
            scrollViewer.Unloaded += ScrollViewer_Unloaded;
        }

        public static double GetHorizontalOffset(DependencyObject depObj)
        {
            return (double)depObj.GetValue(HorizontalOffsetProperty);
        }

        public static double GetVerticalOffset(DependencyObject depObj)
        {
            return (double)depObj.GetValue(VerticalOffsetProperty);
        }

        public static void SetHorizontalOffset(DependencyObject depObj, double value)
        {
            depObj.SetValue(HorizontalOffsetProperty, value);
        }

        public static void SetVerticalOffset(DependencyObject depObj, double value)
        {
            depObj.SetValue(VerticalOffsetProperty, value);
        }

        private static void OnHorizontalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollViewer scrollViewer = d as ScrollViewer;
            if (scrollViewer == null || double.IsNaN((double)e.NewValue))
                return;

            BindOffset(scrollViewer);
            scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
        }

        private static void OnVerticalOffsetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ScrollViewer scrollViewer = d as ScrollViewer;
            if (scrollViewer == null || double.IsNaN((double)e.NewValue))
                return;

            BindOffset(scrollViewer);
            scrollViewer.ScrollToVerticalOffset((double)e.NewValue);
        }

        private static void ScrollChanged(object s, ScrollChangedEventArgs se)
        {
            if (se.VerticalChange != 0)
                SetVerticalOffset(s as ScrollViewer, se.VerticalOffset);

            if (se.HorizontalChange != 0)
                SetHorizontalOffset(s as ScrollViewer, se.HorizontalOffset);
        }

        private static void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
        {
            var scrollViewer = sender as ScrollViewer;
            scrollViewer.ScrollChanged += ScrollChanged;
        }

        private static void ScrollViewer_Unloaded(object sender, RoutedEventArgs e)
        {
            var scrollViewer = sender as ScrollViewer;
            scrollViewer.SetValue(IsScrollPositionBoundProperty, false);

            scrollViewer.ScrollChanged -= ScrollChanged;
            scrollViewer.Loaded -= ScrollViewer_Loaded;
            scrollViewer.Unloaded -= ScrollViewer_Unloaded;
        }
    }
}

1
这很好,但你需要在你的属性更改函数中进行NAN检查,例如 if (d不是ScrollViewer scrollViewer || double.IsNaN((double)e.NewValue)) - user3190036
根据@user3190036的建议进行了修正。 - John K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接