通过viewmodel将scrollviewer滚动到顶部

时间:2011-05-22 14:30:43

标签: wpf mvvm scroll

我正在使用带有MVVM模式的ScrollViewer,并且ScrollViewer包含了一个项目列表,例如

<ScrollViewer>
  <ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn
                Header = "Name"
                DisplayMemberBinding="{Binding Path=Name}"
            />              
        </GridView>
    </ListView.View>
  </ListView>
</ScrollViewer>

listview的项目绑定到viewmodel中的一组对象。我希望每当从集合中添加或删除项目时,scrollviewer都会滚动到顶部 我需要viewmodel来触发事件,而不是在视图的代码隐藏中使用ScrollToTop()方法。

4 个答案:

答案 0 :(得分:20)

恕我直言,最明智的方法是通过AttachedProperty使用“行为”。 AttachedProperty是一种扩展现有控件功能的机制。

首先,创建一个类来保存AtachedProperty,例如:

public class ScrollViewerBehavior
{
    public static bool GetAutoScrollToTop(DependencyObject obj)
    {
        return (bool)obj.GetValue(AutoScrollToTopProperty);
    }

    public static void SetAutoScrollToTop(DependencyObject obj, bool value)
    {
        obj.SetValue(AutoScrollToTopProperty, value);
    }

    public static readonly DependencyProperty AutoScrollToTopProperty =
        DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) =>
            {
                var scrollViewer = o as ScrollViewer;
                if (scrollViewer == null)
                {
                    return;
                }
                if ((bool)e.NewValue)
                {
                    scrollViewer.ScrollToTop();
                    SetAutoScrollToTop(o, false);
                }
            }));
}

此附加属性允许ScrollViewer“神奇地”拥有Boolean类型的新属性,就像您的XAML中的DependencyProperty一样。如果将此属性绑定到ViewModel中的标准属性,例如:

private bool _reset;
public bool Reset
{
    get { return _reset; }
    set
    {
        _reset = value;
        if(PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("Reset"));
    }
}

(同样,名称取决于您)然后将此Reset属性设置为true,您的ScrollViewer将滚动到顶部。 我将AtachedProperty命名为AutoScrollToTop,但名称对此并不重要。

XAML将类似于:

<ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}">
    <ListView>
        <ListView.View>
            <GridView>
                <GridViewColumn
                    Header = "Name"
                    DisplayMemberBinding="{Binding Path=Name}"
                />
            </GridView>
        </ListView.View>
    </ListView>
</ScrollViewer>

注意:my是您的ScrollViewerBehavior班级所在的命名空间。例如:xmlns:my="clr-namespace:MyApp.Behaviors"

最后,您在ViewModel中唯一需要做的就是在您喜欢的时候设置Reset = true,在您添加或删除集合中的元素时。

答案 1 :(得分:2)

创建一个新的ListView控件,扩展Listview并使用这个新的

sin6_scope_id

答案 2 :(得分:-1)

我还遇到了类似的场景,我需要以编程方式分配ScrollViewer的Horizo​​ntalOffset和VerticalOffset。恐怕没有直接约束机制。我做的是一种方式(相信我,我仍然不喜欢我遵循的方法,但我没有找到任何其他选择)。以下是我的建议:

勾选ScrollViewer的Loaded事件,将发送者对象强制转换为ScrollViewer并将其分配给DataContext中的属性(意味着您需要在DataContext中保留ScrollViewer属性,这将在UI中保存ScrollViewer的引用)。在ViewModel中连接ObservableCollection的CollectionChanged事件并使用ScrollViewer属性,您可以调用ScrollToTop()等方法。

这只是一种解决方法。我仍在寻找更好的解决方案。

答案 3 :(得分:-1)

在MVVM中执行此操作的最简单方法是在viewmodel中创建一个事件并从视图中订阅它。然后,在事件处理程序中,调用ScrollToTop

例如,每次修改集合时,都会从viewmodel触发事件,然后由视图决定是否对该事件作出反应并将列表滚动到顶部。

即使这涉及一些代码隐藏并且要求视图知道其视图模型的一部分,它也不会违反MVVM模式,与其他变通方法不同。

public interface IMyViewModel
{
    event EventHandler MyCollectionChanged;
}

public class MyViewModel : IMyViewModel
{
    public event EventHandler MyCollectionChanged;

    // More viewmodel related stuff

    protected virtual void OnMyCollectionChanged(EventArgs e)
    {
        if (MyCollectionChanged != null)
            MyCollectionChanged(this, e);
    }
}

public class MyWindow : Window
{
    public MyWindow(IMyViewModel viewModel)
    {
        this.DataContext = viewModel;
        InitializeComponent();
        (this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler;
    }

    private void MyCollectionChangedEventHandler(object sender, EventArgs e)
    {
        // Do view related stuff
        scrollViewer.ScrollToTop();
    }

}

编辑:但当然,它可以提炼得更多。如果要避免使用代码隐藏,请查找DataEventTriggers。如果你不介意代码隐藏但是担心内存泄漏,请寻找弱事件。

最后,由于您想要的逻辑是100%视图相关的(每次添加或删除项目时都有ListView滚动),您还可以将其实现为行为/附加属性,或者扩展ListView 。这可能会让人感到更加复杂,但我鼓励你对这些选择进行一些思考。